├── .gitignore ├── Contacts.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Contacts-cal.xcscheme │ └── Contacts.xcscheme ├── Contacts ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Contact.h ├── Contact.m ├── ContactCell.h ├── ContactCell.m ├── ContactDetailsViewController.h ├── ContactDetailsViewController.m ├── ContactsListViewController.h ├── ContactsListViewController.m ├── EditContactDetailsViewController.h ├── EditContactDetailsViewController.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app-icon@2x.png │ │ └── app-icon@3x.png │ └── contact-icon.imageset │ │ ├── Contents.json │ │ └── contact-icon@2x.png ├── Info.plist ├── UIColor+Utils.h ├── UIColor+Utils.m └── main.m ├── ContactsUITests ├── AddContactsTests.m ├── Info.plist ├── RemoveContactTests.m └── UpdateContactTests.m ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── appium ├── appium.txt └── features │ ├── add_contact.feature │ ├── remove_contact.feature │ ├── step_definitions │ └── steps.rb │ ├── support │ └── env.rb │ └── update_contact.feature ├── assets ├── appium.png ├── calabash-firewall.png ├── contact-details.png ├── contact-edit.png ├── contact-list.png └── contact-remove.png ├── benchmarks ├── benchmark-appium.sh ├── benchmark-calabash.sh ├── benchmark-ui-automation.sh └── benchmark-xctests.sh ├── calabash ├── calabash.framework │ ├── Headers │ ├── Resources │ ├── Versions │ │ ├── 0.16.4 │ │ ├── A │ │ │ ├── Headers │ │ │ │ ├── CalabashServer.h │ │ │ │ ├── LPCORSResponse.h │ │ │ │ ├── LPHTTPAsyncFileResponse.h │ │ │ │ ├── LPHTTPDataResponse.h │ │ │ │ ├── LPHTTPDynamicFileResponse.h │ │ │ │ ├── LPHTTPFileResponse.h │ │ │ │ ├── LPHTTPResponse.h │ │ │ │ ├── LPIsWebView.h │ │ │ │ ├── LPRoute.h │ │ │ │ ├── LPRouter.h │ │ │ │ ├── LPVersionRoute.h │ │ │ │ ├── LPWebQuery.h │ │ │ │ ├── LPWebViewProtocol.h │ │ │ │ └── UIWebView+LPWebView.h │ │ │ ├── Resources │ │ │ │ └── version │ │ │ └── calabash │ │ └── Current │ └── calabash └── features │ ├── add_contact.feature │ ├── remove_contact.feature │ ├── step_definitions │ ├── calabash_steps.rb │ └── steps.rb │ ├── support │ ├── 01_launch.rb │ ├── 02_pre_stop_hooks.rb │ └── env.rb │ └── update_contact.feature ├── fastlane └── Fastfile └── ui-automation ├── run-tests.sh ├── specs ├── add_contact.js ├── remove_contact.js └── update_contact.js └── test-reports └── test-results.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | # Test Reports/Outputs 56 | ui-automation/test-reports 57 | test_output/ -------------------------------------------------------------------------------- /Contacts.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 145E8E5A1BFC022100307881 /* ContactCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D081BF0DC6F00A388DF /* ContactCell.m */; }; 11 | 145E8E5B1BFC022100307881 /* UIColor+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D031BF0D64100A388DF /* UIColor+Utils.m */; }; 12 | 145E8E5C1BFC022100307881 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CDC1BF0CF3E00A388DF /* AppDelegate.m */; }; 13 | 145E8E5D1BFC022100307881 /* ContactsListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CDF1BF0CF3E00A388DF /* ContactsListViewController.m */; }; 14 | 145E8E5E1BFC022100307881 /* EditContactDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDAF42051BF1EB09008D6FC4 /* EditContactDetailsViewController.m */; }; 15 | 145E8E5F1BFC022100307881 /* Contact.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D0D1BF0E1A200A388DF /* Contact.m */; }; 16 | 145E8E601BFC022100307881 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CD91BF0CF3E00A388DF /* main.m */; }; 17 | 145E8E611BFC022100307881 /* ContactDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CE21BF0CF3E00A388DF /* ContactDetailsViewController.m */; }; 18 | 145E8E641BFC022100307881 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE41BF0CF3E00A388DF /* Main.storyboard */; }; 19 | 145E8E651BFC022100307881 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE91BF0CF3E00A388DF /* LaunchScreen.xib */; }; 20 | 145E8E661BFC022100307881 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE71BF0CF3E00A388DF /* Images.xcassets */; }; 21 | 145E8E6D1BFC03FF00307881 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 145E8E6C1BFC03FF00307881 /* CFNetwork.framework */; }; 22 | 145E8E701BFC047100307881 /* calabash.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 145E8E6E1BFC046500307881 /* calabash.framework */; }; 23 | 14C9494A1BFCB0DC007904B2 /* AddContactsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C949491BFCB0DC007904B2 /* AddContactsTests.m */; }; 24 | 14C9494C1BFCB0ED007904B2 /* RemoveContactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C9494B1BFCB0ED007904B2 /* RemoveContactTests.m */; }; 25 | 14C9494E1BFCB101007904B2 /* UpdateContactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C9494D1BFCB101007904B2 /* UpdateContactTests.m */; }; 26 | CDAF42061BF1EB09008D6FC4 /* EditContactDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDAF42051BF1EB09008D6FC4 /* EditContactDetailsViewController.m */; }; 27 | CDE68CDA1BF0CF3E00A388DF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CD91BF0CF3E00A388DF /* main.m */; }; 28 | CDE68CDD1BF0CF3E00A388DF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CDC1BF0CF3E00A388DF /* AppDelegate.m */; }; 29 | CDE68CE01BF0CF3E00A388DF /* ContactsListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CDF1BF0CF3E00A388DF /* ContactsListViewController.m */; }; 30 | CDE68CE31BF0CF3E00A388DF /* ContactDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68CE21BF0CF3E00A388DF /* ContactDetailsViewController.m */; }; 31 | CDE68CE61BF0CF3E00A388DF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE41BF0CF3E00A388DF /* Main.storyboard */; }; 32 | CDE68CE81BF0CF3E00A388DF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE71BF0CF3E00A388DF /* Images.xcassets */; }; 33 | CDE68CEB1BF0CF3E00A388DF /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = CDE68CE91BF0CF3E00A388DF /* LaunchScreen.xib */; }; 34 | CDE68D041BF0D64100A388DF /* UIColor+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D031BF0D64100A388DF /* UIColor+Utils.m */; }; 35 | CDE68D091BF0DC6F00A388DF /* ContactCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D081BF0DC6F00A388DF /* ContactCell.m */; }; 36 | CDE68D0E1BF0E1A200A388DF /* Contact.m in Sources */ = {isa = PBXBuildFile; fileRef = CDE68D0D1BF0E1A200A388DF /* Contact.m */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | 14C949441BFCB08D007904B2 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = CDE68CCC1BF0CF3E00A388DF /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = CDE68CD31BF0CF3E00A388DF; 45 | remoteInfo = Contacts; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 145E8E6A1BFC022100307881 /* Contacts-cal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Contacts-cal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 145E8E6C1BFC03FF00307881 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 52 | 145E8E6E1BFC046500307881 /* calabash.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = calabash.framework; path = ../calabash/calabash.framework; sourceTree = ""; }; 53 | 14C9493F1BFCB08D007904B2 /* ContactsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ContactsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 14C949431BFCB08D007904B2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 14C949491BFCB0DC007904B2 /* AddContactsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddContactsTests.m; sourceTree = ""; }; 56 | 14C9494B1BFCB0ED007904B2 /* RemoveContactTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RemoveContactTests.m; sourceTree = ""; }; 57 | 14C9494D1BFCB101007904B2 /* UpdateContactTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UpdateContactTests.m; sourceTree = ""; }; 58 | CDAF42041BF1EB09008D6FC4 /* EditContactDetailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditContactDetailsViewController.h; sourceTree = ""; }; 59 | CDAF42051BF1EB09008D6FC4 /* EditContactDetailsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditContactDetailsViewController.m; sourceTree = ""; }; 60 | CDE68CD41BF0CF3E00A388DF /* Contacts-test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Contacts-test.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | CDE68CD81BF0CF3E00A388DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | CDE68CD91BF0CF3E00A388DF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 63 | CDE68CDB1BF0CF3E00A388DF /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 64 | CDE68CDC1BF0CF3E00A388DF /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 65 | CDE68CDE1BF0CF3E00A388DF /* ContactsListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContactsListViewController.h; sourceTree = ""; }; 66 | CDE68CDF1BF0CF3E00A388DF /* ContactsListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContactsListViewController.m; sourceTree = ""; }; 67 | CDE68CE11BF0CF3E00A388DF /* ContactDetailsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContactDetailsViewController.h; sourceTree = ""; }; 68 | CDE68CE21BF0CF3E00A388DF /* ContactDetailsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContactDetailsViewController.m; sourceTree = ""; }; 69 | CDE68CE51BF0CF3E00A388DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 70 | CDE68CE71BF0CF3E00A388DF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 71 | CDE68CEA1BF0CF3E00A388DF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 72 | CDE68D021BF0D64100A388DF /* UIColor+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+Utils.h"; sourceTree = ""; }; 73 | CDE68D031BF0D64100A388DF /* UIColor+Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Utils.m"; sourceTree = ""; }; 74 | CDE68D071BF0DC6F00A388DF /* ContactCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactCell.h; sourceTree = ""; }; 75 | CDE68D081BF0DC6F00A388DF /* ContactCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactCell.m; sourceTree = ""; }; 76 | CDE68D0C1BF0E1A200A388DF /* Contact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Contact.h; sourceTree = ""; }; 77 | CDE68D0D1BF0E1A200A388DF /* Contact.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Contact.m; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | 145E8E621BFC022100307881 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 145E8E6D1BFC03FF00307881 /* CFNetwork.framework in Frameworks */, 86 | 145E8E701BFC047100307881 /* calabash.framework in Frameworks */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | 14C9493C1BFCB08D007904B2 /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | CDE68CD11BF0CF3E00A388DF /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXFrameworksBuildPhase section */ 105 | 106 | /* Begin PBXGroup section */ 107 | 14C949401BFCB08D007904B2 /* ContactsUITests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 14C9494F1BFCB10C007904B2 /* Specs */, 111 | 14C949431BFCB08D007904B2 /* Info.plist */, 112 | ); 113 | path = ContactsUITests; 114 | sourceTree = ""; 115 | }; 116 | 14C9494F1BFCB10C007904B2 /* Specs */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 14C949491BFCB0DC007904B2 /* AddContactsTests.m */, 120 | 14C9494B1BFCB0ED007904B2 /* RemoveContactTests.m */, 121 | 14C9494D1BFCB101007904B2 /* UpdateContactTests.m */, 122 | ); 123 | name = Specs; 124 | sourceTree = ""; 125 | }; 126 | CDE68CCB1BF0CF3E00A388DF = { 127 | isa = PBXGroup; 128 | children = ( 129 | CDE68CD61BF0CF3E00A388DF /* Contacts */, 130 | 14C949401BFCB08D007904B2 /* ContactsUITests */, 131 | CDE68CD51BF0CF3E00A388DF /* Products */, 132 | ); 133 | sourceTree = ""; 134 | }; 135 | CDE68CD51BF0CF3E00A388DF /* Products */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | CDE68CD41BF0CF3E00A388DF /* Contacts-test.app */, 139 | 145E8E6A1BFC022100307881 /* Contacts-cal.app */, 140 | 14C9493F1BFCB08D007904B2 /* ContactsUITests.xctest */, 141 | ); 142 | name = Products; 143 | sourceTree = ""; 144 | }; 145 | CDE68CD61BF0CF3E00A388DF /* Contacts */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | CDE68D011BF0D5E000A388DF /* Categories */, 149 | CDE68D001BF0D52600A388DF /* Classes */, 150 | CDE68CE41BF0CF3E00A388DF /* Main.storyboard */, 151 | CDE68CE71BF0CF3E00A388DF /* Images.xcassets */, 152 | CDE68CE91BF0CF3E00A388DF /* LaunchScreen.xib */, 153 | CDE68CD71BF0CF3E00A388DF /* Supporting Files */, 154 | ); 155 | path = Contacts; 156 | sourceTree = ""; 157 | }; 158 | CDE68CD71BF0CF3E00A388DF /* Supporting Files */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 145E8E6E1BFC046500307881 /* calabash.framework */, 162 | 145E8E6C1BFC03FF00307881 /* CFNetwork.framework */, 163 | CDE68CD81BF0CF3E00A388DF /* Info.plist */, 164 | CDE68CD91BF0CF3E00A388DF /* main.m */, 165 | ); 166 | name = "Supporting Files"; 167 | sourceTree = ""; 168 | }; 169 | CDE68D001BF0D52600A388DF /* Classes */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | CDE68D0F1BF0E1A600A388DF /* Models */, 173 | CDE68D0A1BF0DC7900A388DF /* Views */, 174 | CDE68D0B1BF0DC8400A388DF /* Controllers */, 175 | CDE68CDB1BF0CF3E00A388DF /* AppDelegate.h */, 176 | CDE68CDC1BF0CF3E00A388DF /* AppDelegate.m */, 177 | ); 178 | name = Classes; 179 | sourceTree = ""; 180 | }; 181 | CDE68D011BF0D5E000A388DF /* Categories */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | CDE68D021BF0D64100A388DF /* UIColor+Utils.h */, 185 | CDE68D031BF0D64100A388DF /* UIColor+Utils.m */, 186 | ); 187 | name = Categories; 188 | sourceTree = ""; 189 | }; 190 | CDE68D0A1BF0DC7900A388DF /* Views */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | CDE68D071BF0DC6F00A388DF /* ContactCell.h */, 194 | CDE68D081BF0DC6F00A388DF /* ContactCell.m */, 195 | ); 196 | name = Views; 197 | sourceTree = ""; 198 | }; 199 | CDE68D0B1BF0DC8400A388DF /* Controllers */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | CDE68CDE1BF0CF3E00A388DF /* ContactsListViewController.h */, 203 | CDE68CDF1BF0CF3E00A388DF /* ContactsListViewController.m */, 204 | CDE68CE11BF0CF3E00A388DF /* ContactDetailsViewController.h */, 205 | CDE68CE21BF0CF3E00A388DF /* ContactDetailsViewController.m */, 206 | CDAF42041BF1EB09008D6FC4 /* EditContactDetailsViewController.h */, 207 | CDAF42051BF1EB09008D6FC4 /* EditContactDetailsViewController.m */, 208 | ); 209 | name = Controllers; 210 | sourceTree = ""; 211 | }; 212 | CDE68D0F1BF0E1A600A388DF /* Models */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | CDE68D0C1BF0E1A200A388DF /* Contact.h */, 216 | CDE68D0D1BF0E1A200A388DF /* Contact.m */, 217 | ); 218 | name = Models; 219 | sourceTree = ""; 220 | }; 221 | /* End PBXGroup section */ 222 | 223 | /* Begin PBXNativeTarget section */ 224 | 145E8E581BFC022100307881 /* Contacts-cal */ = { 225 | isa = PBXNativeTarget; 226 | buildConfigurationList = 145E8E671BFC022100307881 /* Build configuration list for PBXNativeTarget "Contacts-cal" */; 227 | buildPhases = ( 228 | 145E8E591BFC022100307881 /* Sources */, 229 | 145E8E621BFC022100307881 /* Frameworks */, 230 | 145E8E631BFC022100307881 /* Resources */, 231 | ); 232 | buildRules = ( 233 | ); 234 | dependencies = ( 235 | ); 236 | name = "Contacts-cal"; 237 | productName = Contacts; 238 | productReference = 145E8E6A1BFC022100307881 /* Contacts-cal.app */; 239 | productType = "com.apple.product-type.application"; 240 | }; 241 | 14C9493E1BFCB08D007904B2 /* ContactsUITests */ = { 242 | isa = PBXNativeTarget; 243 | buildConfigurationList = 14C949481BFCB08D007904B2 /* Build configuration list for PBXNativeTarget "ContactsUITests" */; 244 | buildPhases = ( 245 | 14C9493B1BFCB08D007904B2 /* Sources */, 246 | 14C9493C1BFCB08D007904B2 /* Frameworks */, 247 | 14C9493D1BFCB08D007904B2 /* Resources */, 248 | ); 249 | buildRules = ( 250 | ); 251 | dependencies = ( 252 | 14C949451BFCB08D007904B2 /* PBXTargetDependency */, 253 | ); 254 | name = ContactsUITests; 255 | productName = ContactsUITests; 256 | productReference = 14C9493F1BFCB08D007904B2 /* ContactsUITests.xctest */; 257 | productType = "com.apple.product-type.bundle.ui-testing"; 258 | }; 259 | CDE68CD31BF0CF3E00A388DF /* Contacts */ = { 260 | isa = PBXNativeTarget; 261 | buildConfigurationList = CDE68CFA1BF0CF3E00A388DF /* Build configuration list for PBXNativeTarget "Contacts" */; 262 | buildPhases = ( 263 | CDE68CD01BF0CF3E00A388DF /* Sources */, 264 | CDE68CD11BF0CF3E00A388DF /* Frameworks */, 265 | CDE68CD21BF0CF3E00A388DF /* Resources */, 266 | ); 267 | buildRules = ( 268 | ); 269 | dependencies = ( 270 | ); 271 | name = Contacts; 272 | productName = Contacts; 273 | productReference = CDE68CD41BF0CF3E00A388DF /* Contacts-test.app */; 274 | productType = "com.apple.product-type.application"; 275 | }; 276 | /* End PBXNativeTarget section */ 277 | 278 | /* Begin PBXProject section */ 279 | CDE68CCC1BF0CF3E00A388DF /* Project object */ = { 280 | isa = PBXProject; 281 | attributes = { 282 | LastUpgradeCheck = 0710; 283 | ORGANIZATIONNAME = alexmx; 284 | TargetAttributes = { 285 | 14C9493E1BFCB08D007904B2 = { 286 | CreatedOnToolsVersion = 7.1.1; 287 | TestTargetID = CDE68CD31BF0CF3E00A388DF; 288 | }; 289 | CDE68CD31BF0CF3E00A388DF = { 290 | CreatedOnToolsVersion = 6.4; 291 | }; 292 | }; 293 | }; 294 | buildConfigurationList = CDE68CCF1BF0CF3E00A388DF /* Build configuration list for PBXProject "Contacts" */; 295 | compatibilityVersion = "Xcode 3.2"; 296 | developmentRegion = English; 297 | hasScannedForEncodings = 0; 298 | knownRegions = ( 299 | en, 300 | Base, 301 | ); 302 | mainGroup = CDE68CCB1BF0CF3E00A388DF; 303 | productRefGroup = CDE68CD51BF0CF3E00A388DF /* Products */; 304 | projectDirPath = ""; 305 | projectRoot = ""; 306 | targets = ( 307 | CDE68CD31BF0CF3E00A388DF /* Contacts */, 308 | 145E8E581BFC022100307881 /* Contacts-cal */, 309 | 14C9493E1BFCB08D007904B2 /* ContactsUITests */, 310 | ); 311 | }; 312 | /* End PBXProject section */ 313 | 314 | /* Begin PBXResourcesBuildPhase section */ 315 | 145E8E631BFC022100307881 /* Resources */ = { 316 | isa = PBXResourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 145E8E641BFC022100307881 /* Main.storyboard in Resources */, 320 | 145E8E651BFC022100307881 /* LaunchScreen.xib in Resources */, 321 | 145E8E661BFC022100307881 /* Images.xcassets in Resources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | 14C9493D1BFCB08D007904B2 /* Resources */ = { 326 | isa = PBXResourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | ); 330 | runOnlyForDeploymentPostprocessing = 0; 331 | }; 332 | CDE68CD21BF0CF3E00A388DF /* Resources */ = { 333 | isa = PBXResourcesBuildPhase; 334 | buildActionMask = 2147483647; 335 | files = ( 336 | CDE68CE61BF0CF3E00A388DF /* Main.storyboard in Resources */, 337 | CDE68CEB1BF0CF3E00A388DF /* LaunchScreen.xib in Resources */, 338 | CDE68CE81BF0CF3E00A388DF /* Images.xcassets in Resources */, 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | /* End PBXResourcesBuildPhase section */ 343 | 344 | /* Begin PBXSourcesBuildPhase section */ 345 | 145E8E591BFC022100307881 /* Sources */ = { 346 | isa = PBXSourcesBuildPhase; 347 | buildActionMask = 2147483647; 348 | files = ( 349 | 145E8E5A1BFC022100307881 /* ContactCell.m in Sources */, 350 | 145E8E5B1BFC022100307881 /* UIColor+Utils.m in Sources */, 351 | 145E8E5C1BFC022100307881 /* AppDelegate.m in Sources */, 352 | 145E8E5D1BFC022100307881 /* ContactsListViewController.m in Sources */, 353 | 145E8E5E1BFC022100307881 /* EditContactDetailsViewController.m in Sources */, 354 | 145E8E5F1BFC022100307881 /* Contact.m in Sources */, 355 | 145E8E601BFC022100307881 /* main.m in Sources */, 356 | 145E8E611BFC022100307881 /* ContactDetailsViewController.m in Sources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | 14C9493B1BFCB08D007904B2 /* Sources */ = { 361 | isa = PBXSourcesBuildPhase; 362 | buildActionMask = 2147483647; 363 | files = ( 364 | 14C9494A1BFCB0DC007904B2 /* AddContactsTests.m in Sources */, 365 | 14C9494E1BFCB101007904B2 /* UpdateContactTests.m in Sources */, 366 | 14C9494C1BFCB0ED007904B2 /* RemoveContactTests.m in Sources */, 367 | ); 368 | runOnlyForDeploymentPostprocessing = 0; 369 | }; 370 | CDE68CD01BF0CF3E00A388DF /* Sources */ = { 371 | isa = PBXSourcesBuildPhase; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | CDE68D091BF0DC6F00A388DF /* ContactCell.m in Sources */, 375 | CDE68D041BF0D64100A388DF /* UIColor+Utils.m in Sources */, 376 | CDE68CDD1BF0CF3E00A388DF /* AppDelegate.m in Sources */, 377 | CDE68CE01BF0CF3E00A388DF /* ContactsListViewController.m in Sources */, 378 | CDAF42061BF1EB09008D6FC4 /* EditContactDetailsViewController.m in Sources */, 379 | CDE68D0E1BF0E1A200A388DF /* Contact.m in Sources */, 380 | CDE68CDA1BF0CF3E00A388DF /* main.m in Sources */, 381 | CDE68CE31BF0CF3E00A388DF /* ContactDetailsViewController.m in Sources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXSourcesBuildPhase section */ 386 | 387 | /* Begin PBXTargetDependency section */ 388 | 14C949451BFCB08D007904B2 /* PBXTargetDependency */ = { 389 | isa = PBXTargetDependency; 390 | target = CDE68CD31BF0CF3E00A388DF /* Contacts */; 391 | targetProxy = 14C949441BFCB08D007904B2 /* PBXContainerItemProxy */; 392 | }; 393 | /* End PBXTargetDependency section */ 394 | 395 | /* Begin PBXVariantGroup section */ 396 | CDE68CE41BF0CF3E00A388DF /* Main.storyboard */ = { 397 | isa = PBXVariantGroup; 398 | children = ( 399 | CDE68CE51BF0CF3E00A388DF /* Base */, 400 | ); 401 | name = Main.storyboard; 402 | sourceTree = ""; 403 | }; 404 | CDE68CE91BF0CF3E00A388DF /* LaunchScreen.xib */ = { 405 | isa = PBXVariantGroup; 406 | children = ( 407 | CDE68CEA1BF0CF3E00A388DF /* Base */, 408 | ); 409 | name = LaunchScreen.xib; 410 | sourceTree = ""; 411 | }; 412 | /* End PBXVariantGroup section */ 413 | 414 | /* Begin XCBuildConfiguration section */ 415 | 145E8E681BFC022100307881 /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 419 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 420 | FRAMEWORK_SEARCH_PATHS = ( 421 | "$(inherited)", 422 | "$(PROJECT_DIR)/calabash", 423 | ); 424 | INFOPLIST_FILE = Contacts/Info.plist; 425 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 426 | OTHER_LDFLAGS = ( 427 | "$(inherited)", 428 | "-ObjC", 429 | "-force_load", 430 | "\"$(SOURCE_ROOT)/calabash/calabash.framework/calabash\"", 431 | ); 432 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexmx.$(PRODUCT_NAME:rfc1034identifier)"; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | }; 435 | name = Debug; 436 | }; 437 | 145E8E691BFC022100307881 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 442 | FRAMEWORK_SEARCH_PATHS = ( 443 | "$(inherited)", 444 | "$(PROJECT_DIR)/calabash", 445 | ); 446 | INFOPLIST_FILE = Contacts/Info.plist; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 448 | OTHER_LDFLAGS = ( 449 | "$(inherited)", 450 | "-ObjC", 451 | "-force_load", 452 | "\"$(SOURCE_ROOT)/calabash/calabash.framework/calabash\"", 453 | ); 454 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexmx.$(PRODUCT_NAME:rfc1034identifier)"; 455 | PRODUCT_NAME = "$(TARGET_NAME)"; 456 | }; 457 | name = Release; 458 | }; 459 | 14C949461BFCB08D007904B2 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | DEBUG_INFORMATION_FORMAT = dwarf; 463 | INFOPLIST_FILE = ContactsUITests/Info.plist; 464 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 466 | PRODUCT_BUNDLE_IDENTIFIER = com.alexmx.ContactsUITests; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | TEST_TARGET_NAME = Contacts; 469 | USES_XCTRUNNER = YES; 470 | }; 471 | name = Debug; 472 | }; 473 | 14C949471BFCB08D007904B2 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | INFOPLIST_FILE = ContactsUITests/Info.plist; 477 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 478 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 479 | PRODUCT_BUNDLE_IDENTIFIER = com.alexmx.ContactsUITests; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | TEST_TARGET_NAME = Contacts; 482 | USES_XCTRUNNER = YES; 483 | }; 484 | name = Release; 485 | }; 486 | CDE68CF81BF0CF3E00A388DF /* Debug */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ALWAYS_SEARCH_USER_PATHS = NO; 490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 491 | CLANG_CXX_LIBRARY = "libc++"; 492 | CLANG_ENABLE_MODULES = YES; 493 | CLANG_ENABLE_OBJC_ARC = YES; 494 | CLANG_WARN_BOOL_CONVERSION = YES; 495 | CLANG_WARN_CONSTANT_CONVERSION = YES; 496 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 497 | CLANG_WARN_EMPTY_BODY = YES; 498 | CLANG_WARN_ENUM_CONVERSION = YES; 499 | CLANG_WARN_INT_CONVERSION = YES; 500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 501 | CLANG_WARN_UNREACHABLE_CODE = YES; 502 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 503 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 504 | COPY_PHASE_STRIP = NO; 505 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 506 | ENABLE_STRICT_OBJC_MSGSEND = YES; 507 | ENABLE_TESTABILITY = YES; 508 | GCC_C_LANGUAGE_STANDARD = gnu99; 509 | GCC_DYNAMIC_NO_PIC = NO; 510 | GCC_NO_COMMON_BLOCKS = YES; 511 | GCC_OPTIMIZATION_LEVEL = 0; 512 | GCC_PREPROCESSOR_DEFINITIONS = ( 513 | "DEBUG=1", 514 | "$(inherited)", 515 | ); 516 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 517 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 518 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 519 | GCC_WARN_UNDECLARED_SELECTOR = YES; 520 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 521 | GCC_WARN_UNUSED_FUNCTION = YES; 522 | GCC_WARN_UNUSED_VARIABLE = YES; 523 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 524 | MTL_ENABLE_DEBUG_INFO = YES; 525 | ONLY_ACTIVE_ARCH = YES; 526 | SDKROOT = iphoneos; 527 | }; 528 | name = Debug; 529 | }; 530 | CDE68CF91BF0CF3E00A388DF /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | ALWAYS_SEARCH_USER_PATHS = NO; 534 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 535 | CLANG_CXX_LIBRARY = "libc++"; 536 | CLANG_ENABLE_MODULES = YES; 537 | CLANG_ENABLE_OBJC_ARC = YES; 538 | CLANG_WARN_BOOL_CONVERSION = YES; 539 | CLANG_WARN_CONSTANT_CONVERSION = YES; 540 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 541 | CLANG_WARN_EMPTY_BODY = YES; 542 | CLANG_WARN_ENUM_CONVERSION = YES; 543 | CLANG_WARN_INT_CONVERSION = YES; 544 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 545 | CLANG_WARN_UNREACHABLE_CODE = YES; 546 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 547 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 548 | COPY_PHASE_STRIP = NO; 549 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 550 | ENABLE_NS_ASSERTIONS = NO; 551 | ENABLE_STRICT_OBJC_MSGSEND = YES; 552 | GCC_C_LANGUAGE_STANDARD = gnu99; 553 | GCC_NO_COMMON_BLOCKS = YES; 554 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 555 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 556 | GCC_WARN_UNDECLARED_SELECTOR = YES; 557 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 558 | GCC_WARN_UNUSED_FUNCTION = YES; 559 | GCC_WARN_UNUSED_VARIABLE = YES; 560 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 561 | MTL_ENABLE_DEBUG_INFO = NO; 562 | SDKROOT = iphoneos; 563 | VALIDATE_PRODUCT = YES; 564 | }; 565 | name = Release; 566 | }; 567 | CDE68CFB1BF0CF3E00A388DF /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 571 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 572 | FRAMEWORK_SEARCH_PATHS = ( 573 | "$(inherited)", 574 | "$(PROJECT_DIR)/calabash", 575 | ); 576 | INFOPLIST_FILE = Contacts/Info.plist; 577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 578 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexmx.$(PRODUCT_NAME:rfc1034identifier)"; 579 | PRODUCT_NAME = "$(TARGET_NAME)-test"; 580 | }; 581 | name = Debug; 582 | }; 583 | CDE68CFC1BF0CF3E00A388DF /* Release */ = { 584 | isa = XCBuildConfiguration; 585 | buildSettings = { 586 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 587 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 588 | FRAMEWORK_SEARCH_PATHS = ( 589 | "$(inherited)", 590 | "$(PROJECT_DIR)/calabash", 591 | ); 592 | INFOPLIST_FILE = Contacts/Info.plist; 593 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 594 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexmx.$(PRODUCT_NAME:rfc1034identifier)"; 595 | PRODUCT_NAME = "$(TARGET_NAME)-test"; 596 | }; 597 | name = Release; 598 | }; 599 | /* End XCBuildConfiguration section */ 600 | 601 | /* Begin XCConfigurationList section */ 602 | 145E8E671BFC022100307881 /* Build configuration list for PBXNativeTarget "Contacts-cal" */ = { 603 | isa = XCConfigurationList; 604 | buildConfigurations = ( 605 | 145E8E681BFC022100307881 /* Debug */, 606 | 145E8E691BFC022100307881 /* Release */, 607 | ); 608 | defaultConfigurationIsVisible = 0; 609 | defaultConfigurationName = Release; 610 | }; 611 | 14C949481BFCB08D007904B2 /* Build configuration list for PBXNativeTarget "ContactsUITests" */ = { 612 | isa = XCConfigurationList; 613 | buildConfigurations = ( 614 | 14C949461BFCB08D007904B2 /* Debug */, 615 | 14C949471BFCB08D007904B2 /* Release */, 616 | ); 617 | defaultConfigurationIsVisible = 0; 618 | }; 619 | CDE68CCF1BF0CF3E00A388DF /* Build configuration list for PBXProject "Contacts" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | CDE68CF81BF0CF3E00A388DF /* Debug */, 623 | CDE68CF91BF0CF3E00A388DF /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | CDE68CFA1BF0CF3E00A388DF /* Build configuration list for PBXNativeTarget "Contacts" */ = { 629 | isa = XCConfigurationList; 630 | buildConfigurations = ( 631 | CDE68CFB1BF0CF3E00A388DF /* Debug */, 632 | CDE68CFC1BF0CF3E00A388DF /* Release */, 633 | ); 634 | defaultConfigurationIsVisible = 0; 635 | defaultConfigurationName = Release; 636 | }; 637 | /* End XCConfigurationList section */ 638 | }; 639 | rootObject = CDE68CCC1BF0CF3E00A388DF /* Project object */; 640 | } 641 | -------------------------------------------------------------------------------- /Contacts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Contacts.xcodeproj/xcshareddata/xcschemes/Contacts-cal.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Contacts.xcodeproj/xcshareddata/xcschemes/Contacts.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Contacts/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Contacts/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ContactDetailsViewController.h" 11 | #import "UIColor+Utils.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 21 | { 22 | [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; 23 | 24 | [[UINavigationBar appearance] setTitleTextAttributes:@{ NSForegroundColorAttributeName: [UIColor whiteColor] }]; 25 | [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]]; 26 | [[UINavigationBar appearance] setBarTintColor:[UIColor mainColor]]; 27 | 28 | return YES; 29 | } 30 | 31 | - (void)applicationWillResignActive:(UIApplication *)application { 32 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 33 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 34 | } 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | } 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application { 46 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Contacts/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Contacts/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /Contacts/Contact.h: -------------------------------------------------------------------------------- 1 | // 2 | // Contact.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface Contact : NSObject 13 | 14 | @property (nonatomic, strong) UIImage *icon; 15 | @property (nonatomic, copy) NSString *firstName; 16 | @property (nonatomic, copy) NSString *lastName; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Contacts/Contact.m: -------------------------------------------------------------------------------- 1 | // 2 | // Contact.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "Contact.h" 10 | 11 | @implementation Contact 12 | 13 | - (instancetype)init 14 | { 15 | if (self = [super init]) { 16 | self.icon = [UIImage imageNamed:@"contact-icon"]; 17 | } 18 | 19 | return self; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Contacts/ContactCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactCell.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class Contact; 12 | 13 | @interface ContactCell : UITableViewCell 14 | 15 | @property (nonatomic, strong) UIImage *contactIcon; 16 | @property (nonatomic, copy) NSString *contactName; 17 | 18 | @property (nonatomic, strong) Contact *contact; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Contacts/ContactCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactCell.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "ContactCell.h" 10 | #import "Contact.h" 11 | 12 | @interface ContactCell () 13 | 14 | @property (weak, nonatomic) IBOutlet UIImageView *contactIconImageView; 15 | @property (weak, nonatomic) IBOutlet UILabel *contactNameLabel; 16 | 17 | @end 18 | 19 | @implementation ContactCell 20 | 21 | - (NSString *)contactName 22 | { 23 | return self.contactNameLabel.text; 24 | } 25 | 26 | - (void)setContactName:(NSString *)contactName 27 | { 28 | self.contactNameLabel.text = contactName; 29 | } 30 | 31 | - (UIImage *)contactIcon 32 | { 33 | return self.contactIconImageView.image; 34 | } 35 | 36 | - (void)setContactIcon:(UIImage *)contactIcon 37 | { 38 | self.contactIconImageView.image = contactIcon; 39 | } 40 | 41 | - (void)setContact:(Contact *)contact 42 | { 43 | _contact = contact; 44 | 45 | self.contactName = [NSString stringWithFormat:@"%@ %@", contact.firstName, contact.lastName]; 46 | self.contactIcon = contact.icon; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Contacts/ContactDetailsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactDetailsViewController.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Contact.h" 11 | 12 | @interface ContactDetailsViewController : UIViewController 13 | 14 | @property (nonatomic, strong) Contact *contact; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Contacts/ContactDetailsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactDetailsViewController.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "ContactDetailsViewController.h" 10 | #import "EditContactDetailsViewController.h" 11 | 12 | @interface ContactDetailsViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet UIImageView *contactIconImageView; 15 | @property (weak, nonatomic) IBOutlet UILabel *contactNameLabel; 16 | 17 | @end 18 | 19 | @implementation ContactDetailsViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | 25 | // Update screen 26 | self.contact = self.contact; 27 | 28 | self.view.accessibilityIdentifier = @"com.vc.contactDetails"; 29 | self.contactNameLabel.accessibilityIdentifier = @"com.label.contactDetails"; 30 | } 31 | 32 | - (void)setContact:(Contact *)contact 33 | { 34 | _contact = contact; 35 | 36 | self.contactIconImageView.image = self.contact.icon; 37 | self.contactNameLabel.text = [NSString stringWithFormat:@"%@ %@", self.contact.firstName, self.contact.lastName]; 38 | } 39 | 40 | #pragma mark - Segues 41 | 42 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 43 | { 44 | if ([[segue identifier] isEqualToString:@"showEditScreen"]) { 45 | UINavigationController *navC = segue.destinationViewController; 46 | EditContactDetailsViewController *editContactVC = [navC.viewControllers firstObject]; 47 | editContactVC.contact = self.contact; 48 | editContactVC.delegate = self; 49 | } 50 | } 51 | 52 | #pragma mark - EditContactDetailsViewControllerDelegate 53 | 54 | - (void)editContactDetailsViewController:(EditContactDetailsViewController *)editVC 55 | didEndEditingContact:(Contact *)contact 56 | { 57 | self.contact = contact; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Contacts/ContactsListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsListViewController.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ContactsListViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Contacts/ContactsListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsListViewController.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "ContactsListViewController.h" 10 | #import "ContactDetailsViewController.h" 11 | #import "EditContactDetailsViewController.h" 12 | #import "ContactCell.h" 13 | #import "Contact.h" 14 | 15 | @interface ContactsListViewController () 16 | 17 | @property (nonatomic, strong) NSMutableArray *contacts; 18 | 19 | @end 20 | 21 | @implementation ContactsListViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | 27 | self.navigationItem.leftBarButtonItem = self.editButtonItem; 28 | 29 | self.view.accessibilityIdentifier = @"com.vc.contactsList"; 30 | } 31 | 32 | - (void)viewWillAppear:(BOOL)animated 33 | { 34 | [super viewWillAppear:animated]; 35 | 36 | [self.tableView reloadData]; 37 | } 38 | 39 | #pragma mark - Accessors 40 | 41 | - (NSMutableArray *)contacts 42 | { 43 | if (!_contacts) { 44 | _contacts = [NSMutableArray array]; 45 | } 46 | 47 | return _contacts; 48 | } 49 | 50 | #pragma mark - Segues 51 | 52 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 53 | { 54 | if ([[segue identifier] isEqualToString:@"showDetail"]) { 55 | NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; 56 | Contact *contact = self.contacts[indexPath.row]; 57 | [[segue destinationViewController] setContact:contact]; 58 | } else if ([[segue identifier] isEqualToString:@"showEditScreen"]) { 59 | UINavigationController *navC = segue.destinationViewController; 60 | EditContactDetailsViewController *editContactVC = [navC.viewControllers firstObject]; 61 | editContactVC.delegate = self; 62 | } 63 | } 64 | 65 | #pragma mark - UITableViewDataSource 66 | 67 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 68 | { 69 | return 1; 70 | } 71 | 72 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 73 | { 74 | return self.contacts.count; 75 | } 76 | 77 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 78 | { 79 | ContactCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 80 | cell.contact = self.contacts[indexPath.row]; 81 | cell.isAccessibilityElement = YES; 82 | cell.accessibilityIdentifier = @"com.cell.contactsList"; 83 | 84 | return cell; 85 | } 86 | 87 | #pragma mark - UITableViewDelegate 88 | 89 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 90 | { 91 | return YES; 92 | } 93 | 94 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 95 | { 96 | if (editingStyle == UITableViewCellEditingStyleDelete) { 97 | [self.contacts removeObjectAtIndex:indexPath.row]; 98 | [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; 99 | } 100 | } 101 | 102 | #pragma mark - EditContactDetailsViewControllerDelegate 103 | 104 | - (void)editContactDetailsViewController:(EditContactDetailsViewController *)editVC 105 | didEndEditingContact:(Contact *)contact 106 | { 107 | [self.contacts addObject:contact]; 108 | [self.tableView reloadData]; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Contacts/EditContactDetailsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // EditContactDetailsViewController.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/10/15. 6 | // Copyright © 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Contact.h" 11 | 12 | @class EditContactDetailsViewController; 13 | 14 | @protocol EditContactDetailsViewControllerDelegate 15 | 16 | - (void)editContactDetailsViewController:(EditContactDetailsViewController *)editVC didEndEditingContact:(Contact *)contact; 17 | 18 | @end 19 | 20 | @interface EditContactDetailsViewController : UITableViewController 21 | 22 | @property (nonatomic, strong) Contact *contact; 23 | @property (nonatomic, weak) id delegate; 24 | 25 | - (instancetype)initWithContact:(Contact *)contact; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Contacts/EditContactDetailsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // EditContactDetailsViewController.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/10/15. 6 | // Copyright © 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "EditContactDetailsViewController.h" 10 | 11 | @interface EditContactDetailsViewController () 12 | 13 | @property (weak, nonatomic) IBOutlet UITextField *firstNameTextField; 14 | @property (weak, nonatomic) IBOutlet UITextField *lastNameTextField; 15 | 16 | @end 17 | 18 | @implementation EditContactDetailsViewController 19 | 20 | - (instancetype)initWithContact:(Contact *)contact 21 | { 22 | if (self = [super init]) { 23 | _contact = contact; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | 33 | if (self.contact) { 34 | self.firstNameTextField.text = self.contact.firstName; 35 | self.lastNameTextField.text = self.contact.lastName; 36 | } 37 | 38 | self.view.accessibilityIdentifier = @"com.vc.contactsEdit"; 39 | 40 | self.firstNameTextField.accessibilityIdentifier = @"com.textfield.firstName"; 41 | self.lastNameTextField.accessibilityIdentifier = @"com.textfield.lastName"; 42 | } 43 | 44 | - (void)viewWillDisappear:(BOOL)animated 45 | { 46 | [self.firstNameTextField resignFirstResponder]; 47 | [self.lastNameTextField resignFirstResponder]; 48 | 49 | [super viewWillDisappear:animated]; 50 | } 51 | 52 | - (IBAction)didCancel:(id)sender 53 | { 54 | [self dismissViewControllerAnimated:YES completion:nil]; 55 | } 56 | 57 | - (IBAction)didFinish:(id)sender 58 | { 59 | if (self.firstNameTextField.text.length || self.lastNameTextField.text.length) { 60 | if ([self.delegate respondsToSelector:@selector(editContactDetailsViewController:didEndEditingContact:)]) { 61 | 62 | if (!self.contact) { 63 | self.contact = [[Contact alloc] init]; 64 | } 65 | 66 | self.contact.firstName = self.firstNameTextField.text; 67 | self.contact.lastName = self.lastNameTextField.text; 68 | 69 | [self.delegate editContactDetailsViewController:self didEndEditingContact:self.contact]; 70 | [self dismissViewControllerAnimated:YES completion:nil]; 71 | } 72 | } 73 | } 74 | 75 | #pragma mark - UITextFieldDelegate 76 | 77 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 78 | { 79 | return (textField == self.firstNameTextField) ? 80 | [self.lastNameTextField becomeFirstResponder] : [textField resignFirstResponder]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Contacts/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "size" : "60x60", 25 | "idiom" : "iphone", 26 | "filename" : "app-icon@2x.png", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "size" : "60x60", 31 | "idiom" : "iphone", 32 | "filename" : "app-icon@3x.png", 33 | "scale" : "3x" 34 | } 35 | ], 36 | "info" : { 37 | "version" : 1, 38 | "author" : "xcode" 39 | } 40 | } -------------------------------------------------------------------------------- /Contacts/Images.xcassets/AppIcon.appiconset/app-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/Contacts/Images.xcassets/AppIcon.appiconset/app-icon@2x.png -------------------------------------------------------------------------------- /Contacts/Images.xcassets/AppIcon.appiconset/app-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/Contacts/Images.xcassets/AppIcon.appiconset/app-icon@3x.png -------------------------------------------------------------------------------- /Contacts/Images.xcassets/contact-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "contact-icon@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Contacts/Images.xcassets/contact-icon.imageset/contact-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/Contacts/Images.xcassets/contact-icon.imageset/contact-icon@2x.png -------------------------------------------------------------------------------- /Contacts/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | 47 | UIViewControllerBasedStatusBarAppearance 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Contacts/UIColor+Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Utils.h 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIColor (Utils) 12 | 13 | + (instancetype)mainColor; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Contacts/UIColor+Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Utils.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import "UIColor+Utils.h" 10 | 11 | #define RGBA(r, g, b, a) [UIColor colorWithRed:r/255. green:g/255. blue:b/255. alpha:a] 12 | 13 | @implementation UIColor (Utils) 14 | 15 | + (instancetype)mainColor 16 | { 17 | return RGBA(255, 45, 85, 1); 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Contacts/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Contacts 4 | // 5 | // Created by Alexandru Maimescu on 11/9/15. 6 | // Copyright (c) 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ContactsUITests/AddContactsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AddContactsTests.m 3 | // Contacts 4 | // 5 | // Created by Alex Maimescu on 11/18/15. 6 | // Copyright © 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AddContactsTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation AddContactsTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | 21 | self.continueAfterFailure = NO; 22 | [[[XCUIApplication alloc] init] launch]; 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testAddContact 31 | { 32 | XCUIApplication *app = [[XCUIApplication alloc] init]; 33 | [app.navigationBars[@"Contacts"].buttons[@"Add"] tap]; 34 | 35 | XCUIElement *comVcContactseditTable = app.tables[@"com.vc.contactsEdit"]; 36 | XCUIElement *comTextfieldFirstnameTextField = comVcContactseditTable.textFields[@"com.textfield.firstName"]; 37 | [comTextfieldFirstnameTextField tap]; 38 | [comTextfieldFirstnameTextField typeText:@"Jon"]; 39 | 40 | XCUIElement *comTextfieldLastnameTextField = comVcContactseditTable.textFields[@"com.textfield.lastName"]; 41 | [comTextfieldLastnameTextField tap]; 42 | [comTextfieldLastnameTextField tap]; 43 | [comTextfieldLastnameTextField typeText:@"Snow"]; 44 | [app.navigationBars[@"Edit Contact Details"].buttons[@"Done"] tap]; 45 | 46 | XCTAssertNotNil(app.tables[@"com.vc.contactsList"].cells[@"Jon Snow"]); 47 | XCTAssertEqual(app.tables[@"com.vc.contactsList"].cells.count, 1); 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /ContactsUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ContactsUITests/RemoveContactTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RemoveContactTests.m 3 | // Contacts 4 | // 5 | // Created by Alex Maimescu on 11/18/15. 6 | // Copyright © 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RemoveContactTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation RemoveContactTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | 21 | self.continueAfterFailure = NO; 22 | [[[XCUIApplication alloc] init] launch]; 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testRemoveContact 31 | { 32 | XCUIApplication *app = [[XCUIApplication alloc] init]; 33 | XCUIElement *contactsNavigationBar = app.navigationBars[@"Contacts"]; 34 | [contactsNavigationBar.buttons[@"Add"] tap]; 35 | 36 | XCUIElement *comVcContactseditTable = app.tables[@"com.vc.contactsEdit"]; 37 | XCUIElement *comTextfieldFirstnameTextField = comVcContactseditTable.textFields[@"com.textfield.firstName"]; 38 | [comTextfieldFirstnameTextField tap]; 39 | [comTextfieldFirstnameTextField typeText:@"Jon"]; 40 | 41 | XCUIElement *comTextfieldLastnameTextField = comVcContactseditTable.textFields[@"com.textfield.lastName"]; 42 | [comTextfieldLastnameTextField tap]; 43 | [comTextfieldLastnameTextField tap]; 44 | [comTextfieldLastnameTextField typeText:@"Snow"]; 45 | [app.navigationBars[@"Edit Contact Details"].buttons[@"Done"] tap]; 46 | [contactsNavigationBar.buttons[@"Edit"] tap]; 47 | 48 | XCUIElement *comVcContactslistTable = app.tables[@"com.vc.contactsList"]; 49 | [comVcContactslistTable.buttons[@"Delete Jon Snow"] tap]; 50 | [comVcContactslistTable.buttons[@"Delete"] tap]; 51 | 52 | XCTAssertEqual(app.tables[@"com.vc.contactsList"].cells.count, 0); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ContactsUITests/UpdateContactTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateContactTests.m 3 | // Contacts 4 | // 5 | // Created by Alex Maimescu on 11/18/15. 6 | // Copyright © 2015 alexmx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UpdateContactTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation UpdateContactTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | 21 | self.continueAfterFailure = NO; 22 | [[[XCUIApplication alloc] init] launch]; 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testUpdateContact 31 | { 32 | 33 | XCUIApplication *app = [[XCUIApplication alloc] init]; 34 | [app.navigationBars[@"Contacts"].buttons[@"Add"] tap]; 35 | 36 | XCUIElement *comVcContactseditTable2 = app.tables[@"com.vc.contactsEdit"]; 37 | XCUIElement *comTextfieldFirstnameTextField = comVcContactseditTable2.textFields[@"com.textfield.firstName"]; 38 | [comTextfieldFirstnameTextField tap]; 39 | [comTextfieldFirstnameTextField typeText:@"Jon"]; 40 | 41 | XCUIElement *comTextfieldLastnameTextField = comVcContactseditTable2.textFields[@"com.textfield.lastName"]; 42 | [comTextfieldLastnameTextField tap]; 43 | [comTextfieldLastnameTextField typeText:@"Snow"]; 44 | 45 | XCUIElement *doneButton = app.navigationBars[@"Edit Contact Details"].buttons[@"Done"]; 46 | [doneButton tap]; 47 | 48 | XCUIApplication *app2 = app; 49 | [app2.tables[@"com.vc.contactsList"].staticTexts[@"Jon Snow"] tap]; 50 | 51 | XCUIElement *contactNavigationBar = app.navigationBars[@"Contact"]; 52 | [contactNavigationBar.buttons[@"Edit"] tap]; 53 | [comTextfieldFirstnameTextField tap]; 54 | 55 | XCUIElement *comVcContactseditTable = app2.tables[@"com.vc.contactsEdit"]; 56 | [comVcContactseditTable.buttons[@"Clear text"] tap]; 57 | [comTextfieldFirstnameTextField typeText:@"Robb"]; 58 | [comTextfieldLastnameTextField tap]; 59 | [comTextfieldLastnameTextField tap]; 60 | [comVcContactseditTable.buttons[@"Clear text"] tap]; 61 | [comTextfieldLastnameTextField typeText:@"Stark"]; 62 | [doneButton tap]; 63 | [contactNavigationBar.buttons[@"Contacts"] tap]; 64 | 65 | XCTAssertNotNil(app.tables[@"com.vc.contactsList"].cells[@"Robb Stark"]); 66 | XCTAssertEqual(app.tables[@"com.vc.contactsList"].cells.count, 1); 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://www.rubygems.org" 2 | 3 | gem 'calabash-cucumber', '>= 0.16.4', '< 1.0' 4 | gem 'fastlane', '~>1.36.4' 5 | gem 'appium_lib', '~>8.0.0' 6 | gem 'appium_console', '~>1.0.4' 7 | gem 'rspec', '~>3.1.0' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://www.rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.2) 5 | activesupport (4.2.5) 6 | i18n (~> 0.7) 7 | json (~> 1.7, >= 1.7.7) 8 | minitest (~> 5.1) 9 | thread_safe (~> 0.3, >= 0.3.4) 10 | tzinfo (~> 1.1) 11 | addressable (2.3.8) 12 | appium_console (1.0.4) 13 | appium_lib (>= 1.0.0) 14 | bond (~> 0.5.1) 15 | pry (~> 0.10.1) 16 | spec (>= 5.3.4) 17 | appium_lib (8.0.0) 18 | awesome_print (~> 1.6) 19 | json (~> 1.8) 20 | nokogiri (~> 1.6.6) 21 | selenium-webdriver (~> 2.48) 22 | toml (~> 0.0) 23 | autoparse (0.3.3) 24 | addressable (>= 2.3.1) 25 | extlib (>= 0.9.15) 26 | multi_json (>= 1.0.0) 27 | awesome_print (1.6.1) 28 | babosa (1.0.2) 29 | blankslate (2.1.2.4) 30 | bond (0.5.1) 31 | builder (3.2.2) 32 | calabash-common (0.0.2) 33 | calabash-cucumber (0.16.4) 34 | CFPropertyList 35 | awesome_print 36 | bundler (~> 1.3) 37 | calabash-common (~> 0.0.2) 38 | cucumber (~> 1.3.17) 39 | edn (>= 1.0.6, < 2.0) 40 | geocoder (>= 1.1.8, < 2.0) 41 | httpclient (>= 2.3.2, < 3.0) 42 | json 43 | run_loop (>= 1.5.5, < 2.0) 44 | sim_launcher (~> 0.4.13) 45 | slowhandcuke (~> 0.0.3) 46 | cert (1.2.3) 47 | fastlane_core (>= 0.19.0, < 1.0.0) 48 | spaceship (>= 0.6.0) 49 | childprocess (0.5.8) 50 | ffi (~> 1.0, >= 1.0.11) 51 | chronic_duration (0.10.6) 52 | numerizer (~> 0.1.1) 53 | claide (0.9.1) 54 | coderay (1.1.0) 55 | colored (1.2) 56 | command_runner_ng (0.1.0) 57 | commander (4.3.5) 58 | highline (~> 1.7.2) 59 | credentials_manager (0.11.0) 60 | colored 61 | highline (>= 1.7.1) 62 | security 63 | cucumber (1.3.20) 64 | builder (>= 2.1.2) 65 | diff-lcs (>= 1.1.3) 66 | gherkin (~> 2.12) 67 | multi_json (>= 1.7.5, < 2.0) 68 | multi_test (>= 0.1.2) 69 | deliver (1.5.1) 70 | credentials_manager (>= 0.9.0) 71 | fastimage (~> 1.6.3) 72 | fastlane_core (>= 0.19.0, < 1.0.0) 73 | plist (~> 3.1.0) 74 | spaceship (>= 0.14.0, <= 1.0.0) 75 | diff-lcs (1.2.5) 76 | domain_name (0.5.25) 77 | unf (>= 0.0.5, < 1.0.0) 78 | dotenv (2.0.2) 79 | edn (1.1.0) 80 | excon (0.45.4) 81 | extlib (0.9.16) 82 | faraday (0.9.2) 83 | multipart-post (>= 1.2, < 3) 84 | faraday_middleware (0.10.0) 85 | faraday (>= 0.7.4, < 0.10) 86 | fastimage (1.6.8) 87 | addressable (~> 2.3, >= 2.3.5) 88 | fastlane (1.36.4) 89 | addressable (~> 2.3.8) 90 | cert (>= 1.1.0, < 2.0.0) 91 | credentials_manager (>= 0.10.0, < 1.0.0) 92 | deliver (>= 1.5.0, < 2.0.0) 93 | fastlane_core (>= 0.26.1, < 1.0.0) 94 | frameit (>= 2.2.2, < 3.0.0) 95 | gym (>= 1.1.1, < 2.0.0) 96 | krausefx-shenzhen (>= 0.14.6) 97 | pbxplorer (~> 1.0.0) 98 | pem (>= 1.0.0, < 2.0.0) 99 | pilot (>= 1.0.0, < 2.0.0) 100 | plist (~> 3.1.0) 101 | produce (>= 1.0.0, < 2.0.0) 102 | rest-client (~> 1.8.0) 103 | scan (>= 0.2.0, < 1.0.0) 104 | sigh (>= 1.1.1, < 2.0.0) 105 | slack-notifier (~> 1.3) 106 | snapshot (>= 1.0.4, < 2.0.0) 107 | spaceship (>= 0.13.0, < 1.0.0) 108 | supply (>= 0.2.1, < 1.0.0) 109 | terminal-notifier (~> 1.6.2) 110 | terminal-table (~> 1.4.5) 111 | xcodeproj (>= 0.20, < 1.0.0) 112 | xcpretty (>= 0.2.1) 113 | fastlane_core (0.26.5) 114 | babosa 115 | colored 116 | commander (>= 4.3.5) 117 | credentials_manager (>= 0.11.0, < 1.0.0) 118 | excon (~> 0.45.0) 119 | highline (>= 1.7.2) 120 | json 121 | multi_json 122 | plist (~> 3.1) 123 | rubyzip (~> 1.1.6) 124 | sentry-raven (~> 0.15) 125 | terminal-table (~> 1.4.5) 126 | ffi (1.9.10) 127 | frameit (2.4.0) 128 | deliver (> 0.3) 129 | fastimage (~> 1.6.3) 130 | fastlane_core (>= 0.16.0, < 1.0.0) 131 | mini_magick (~> 4.0.2) 132 | geocoder (1.2.12) 133 | gherkin (2.12.2) 134 | multi_json (~> 1.3) 135 | google-api-client (0.8.6) 136 | activesupport (>= 3.2) 137 | addressable (~> 2.3) 138 | autoparse (~> 0.3) 139 | extlib (~> 0.9) 140 | faraday (~> 0.9) 141 | googleauth (~> 0.3) 142 | launchy (~> 2.4) 143 | multi_json (~> 1.10) 144 | retriable (~> 1.4) 145 | signet (~> 0.6) 146 | googleauth (0.4.2) 147 | faraday (~> 0.9) 148 | jwt (~> 1.4) 149 | logging (~> 2.0) 150 | memoist (~> 0.12) 151 | multi_json (~> 1.11) 152 | signet (~> 0.6) 153 | gym (1.1.6) 154 | fastlane_core (>= 0.25.0, < 1.0.0) 155 | plist 156 | rubyzip (>= 1.1.7) 157 | terminal-table 158 | xcpretty (>= 0.2.1) 159 | highline (1.7.8) 160 | http-cookie (1.0.2) 161 | domain_name (~> 0.5) 162 | httpclient (2.7.0.1) 163 | i18n (0.7.0) 164 | json (1.8.3) 165 | jwt (1.5.2) 166 | krausefx-shenzhen (0.14.6) 167 | commander (~> 4.3) 168 | dotenv (>= 0.7) 169 | faraday (~> 0.9) 170 | faraday_middleware (~> 0.9) 171 | highline (>= 1.7.2) 172 | json (~> 1.8) 173 | net-sftp (~> 2.1.2) 174 | plist (~> 3.1.0) 175 | rubyzip (~> 1.1) 176 | security (~> 0.1.3) 177 | terminal-table (~> 1.4.5) 178 | launchy (2.4.3) 179 | addressable (~> 2.3) 180 | little-plugger (1.1.4) 181 | logging (2.0.0) 182 | little-plugger (~> 1.1) 183 | multi_json (~> 1.10) 184 | memoist (0.12.0) 185 | method_source (0.8.2) 186 | mime-types (2.99) 187 | mini_magick (4.0.4) 188 | mini_portile (0.6.2) 189 | minitest (5.8.3) 190 | multi_json (1.11.2) 191 | multi_test (0.1.2) 192 | multi_xml (0.5.5) 193 | multipart-post (2.0.0) 194 | net-sftp (2.1.2) 195 | net-ssh (>= 2.6.5) 196 | net-ssh (3.0.1) 197 | netrc (0.11.0) 198 | nokogiri (1.6.6.4) 199 | mini_portile (~> 0.6.0) 200 | numerizer (0.1.1) 201 | parslet (1.5.0) 202 | blankslate (~> 2.0) 203 | pbxplorer (1.0.0) 204 | pem (1.0.1) 205 | fastlane_core (>= 0.21.0, < 1.0.0) 206 | spaceship (>= 0.12.0, < 1.0.0) 207 | pilot (1.0.1) 208 | credentials_manager (>= 0.3.0) 209 | fastlane_core (>= 0.16.1, < 1.0.0) 210 | spaceship (>= 0.14.0, < 1.0.0) 211 | terminal-table (~> 1.4.5) 212 | plist (3.1.0) 213 | produce (1.0.0) 214 | fastlane_core (>= 0.22.3, < 1.0.0) 215 | spaceship (>= 0.12.0) 216 | pry (0.10.3) 217 | coderay (~> 1.1.0) 218 | method_source (~> 0.8.1) 219 | slop (~> 3.4) 220 | rack (1.6.4) 221 | rack-protection (1.5.3) 222 | rack 223 | rest-client (1.8.0) 224 | http-cookie (>= 1.0.2, < 2.0) 225 | mime-types (>= 1.16, < 3.0) 226 | netrc (~> 0.7) 227 | retriable (1.4.1) 228 | rouge (1.10.1) 229 | rspec (3.1.0) 230 | rspec-core (~> 3.1.0) 231 | rspec-expectations (~> 3.1.0) 232 | rspec-mocks (~> 3.1.0) 233 | rspec-core (3.1.7) 234 | rspec-support (~> 3.1.0) 235 | rspec-expectations (3.1.2) 236 | diff-lcs (>= 1.2.0, < 2.0) 237 | rspec-support (~> 3.1.0) 238 | rspec-mocks (3.1.3) 239 | rspec-support (~> 3.1.0) 240 | rspec-support (3.1.2) 241 | rubyzip (1.1.7) 242 | run_loop (1.5.6) 243 | CFPropertyList (~> 2.2) 244 | awesome_print (~> 1.2) 245 | command_runner_ng (>= 0.0.2) 246 | json (~> 1.8) 247 | retriable (>= 1.3.3.1, < 2.1) 248 | thor (>= 0.18.1, < 1.0) 249 | scan (0.3.1) 250 | fastlane_core (>= 0.25.3, < 1.0.0) 251 | slack-notifier (~> 1.3) 252 | terminal-table 253 | xcpretty (>= 0.2.1) 254 | xcpretty-travis-formatter (>= 0.0.3) 255 | security (0.1.3) 256 | selenium-webdriver (2.48.1) 257 | childprocess (~> 0.5) 258 | multi_json (~> 1.0) 259 | rubyzip (~> 1.0) 260 | websocket (~> 1.0) 261 | sentry-raven (0.15.2) 262 | faraday (>= 0.7.6) 263 | sigh (1.1.3) 264 | fastlane_core (>= 0.19.0, < 1.0.0) 265 | plist (~> 3.1) 266 | spaceship (>= 0.12.3) 267 | signet (0.6.1) 268 | addressable (~> 2.3) 269 | extlib (~> 0.9) 270 | faraday (~> 0.9) 271 | jwt (~> 1.5) 272 | multi_json (~> 1.10) 273 | sim_launcher (0.4.13) 274 | sinatra 275 | sinatra (1.4.6) 276 | rack (~> 1.4) 277 | rack-protection (~> 1.4) 278 | tilt (>= 1.3, < 3) 279 | slack-notifier (1.4.0) 280 | slop (3.6.0) 281 | slowhandcuke (0.0.3) 282 | cucumber 283 | snapshot (1.1.1) 284 | fastimage (~> 1.6.3) 285 | fastlane_core (>= 0.21.0, < 1.0.0) 286 | plist (~> 3.1.0) 287 | xcpretty (>= 0.2.1) 288 | spaceship (0.14.2) 289 | colored 290 | credentials_manager (>= 0.9.0) 291 | faraday (~> 0.9) 292 | faraday_middleware (~> 0.9) 293 | fastimage (~> 1.6) 294 | multi_xml (~> 0.5) 295 | plist (~> 3.1) 296 | pry 297 | spec (5.3.4) 298 | chronic_duration (~> 0.10.2) 299 | supply (0.2.2) 300 | credentials_manager (>= 0.10.0) 301 | fastlane_core (>= 0.19.0) 302 | google-api-client (~> 0.8.6) 303 | terminal-notifier (1.6.3) 304 | terminal-table (1.4.5) 305 | thor (0.19.1) 306 | thread_safe (0.3.5) 307 | tilt (2.0.1) 308 | toml (0.1.2) 309 | parslet (~> 1.5.0) 310 | tzinfo (1.2.2) 311 | thread_safe (~> 0.1) 312 | unf (0.1.4) 313 | unf_ext 314 | unf_ext (0.0.7.1) 315 | websocket (1.2.2) 316 | xcodeproj (0.28.2) 317 | activesupport (>= 3) 318 | claide (~> 0.9.1) 319 | colored (~> 1.2) 320 | xcpretty (0.2.1) 321 | rouge (~> 1.8) 322 | xcpretty-travis-formatter (0.0.4) 323 | xcpretty (~> 0.2, >= 0.0.7) 324 | 325 | PLATFORMS 326 | ruby 327 | 328 | DEPENDENCIES 329 | appium_console (~> 1.0.4) 330 | appium_lib (~> 8.0.0) 331 | calabash-cucumber (>= 0.16.4, < 1.0) 332 | fastlane (~> 1.36.4) 333 | rspec (~> 3.1.0) 334 | 335 | BUNDLED WITH 336 | 1.10.6 337 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Maimescu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS UI Automation Overview 2 | [![Twitter: @amaimescu](https://img.shields.io/badge/contact-%40amaimescu-blue.svg)](https://twitter.com/amaimescu) 3 | [![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/alexmx/ios-ui-automation-overview/blob/master/LICENSE) 4 | 5 | An overview of popular iOS UI Automation solutions which will help you to decide which one to use. 6 | 7 | Covered solutions: 8 | * [x] **UI Tests (XCTest);** 9 | * [x] **UI Automation;** 10 | * [x] **Appium;** 11 | * [x] **Calabash;** 12 | * [ ] **KIF** 13 | 14 | In order to present proper difference between automation solutions, the same app was covered with the same testing scenarios. 15 | Demo application represents a simple **Contacts** app which allows us to perform basic CRUD operations over the *Contact* entity. 16 | 17 | #### Demo app 18 | 19 | Contact List | Contact Details | Edit Contact | Remove Contact 20 | ------------ | ------------- | ------------- | ------------- 21 | ![Contact List](/assets/contact-list.png) | ![Contact Details](/assets/contact-details.png) | ![Edit Contact](/assets/contact-edit.png) | ![Remove Contact](/assets/contact-remove.png) 22 | 23 | ## Scenarios 24 | 25 | **Add contact:** 26 | 27 | ``` 28 | Scenario: User can add a new contact in the contacts list 29 | Given I see the contacts list screen 30 | When I press on "Add" button 31 | Then The screen "Edit Contact Details" appears 32 | And I enter "Jon" in the "first name" textfield 33 | And I enter "Snow" in the "last name" textfield 34 | When I press on "Done" button 35 | Then I see the "Jon Snow" contact in the contact list 36 | ``` 37 | 38 | **Update contact:** 39 | 40 | ``` 41 | Scenario: User can update an existing contact in the contacts list 42 | Given I see the contacts list screen 43 | Given I see at least one contact in the list 44 | When I press on first contact 45 | Then The screen "Contact Details" appears 46 | When I press on "Edit" button 47 | Then The screen "Edit Contact Details" appears 48 | And I enter "Robb" in the "first name" textfield 49 | And I enter "Stark" in the "last name" textfield 50 | When I press on "Done" button 51 | Then The screen "Contact Details" appears 52 | And I see the "Robb Stark" contact in the contact details 53 | When I press on "Back" button 54 | Then I see the "Robb Stark" contact in the contact list 55 | ``` 56 | 57 | **Remove contact:** 58 | 59 | ``` 60 | Scenario: User can remove an existing contact from the contacts list 61 | Given I see the contacts list screen 62 | Given I see at least one contact in the list 63 | When I press on "Edit" button 64 | And I remove the first contact 65 | Then Contacts list is empty 66 | ``` 67 | 68 | ## Installation 69 | We will use `bundler` to install dependencies such as `calabash`, `appium`, `fastlane`. 70 | Make sure that [`bundler`](http://bundler.io/) is installed on your machine: 71 | 72 | ```bash 73 | # Check bundler version 74 | bundler --version 75 | 76 | # Install bundler if not installed 77 | sudo gem install bundler 78 | ``` 79 | 80 | Afer `bundler` is installed run: 81 | ```bash 82 | bundle install 83 | ``` 84 | If you get an error related to `nokogiri` installation, please consider the steps below: 85 | ```bash 86 | # Install libxml2 library using homebrew 87 | brew install libxml2 88 | 89 | # Install 'nokogiri' gem manually 90 | sudo env ARCHFLAGS="-arch x86_64" gem install nokogiri:1.6.6.4 -- --with-xml=/usr/local/Cellar/libxml2/2.9.2 91 | 92 | # Continue installation of dependencies 93 | bundle install 94 | ``` 95 | 96 | For **Appium** we will need to install Appium server separately. For this demo we will use Appium server standalone app. 97 | ![Appium Standalone App](/assets/appium.png) 98 | Check out [this guide](https://github.com/appium/appium) for more details related to Appium server installation. 99 | 100 | ## Run Tests 101 | 102 | For all solutions there are two options to run the tests, manual run or using `fastlane` tools. 103 | [Fastlane](https://github.com/fastlane/fastlane) is a tool which lets you define and run your deployment pipelines for different environments. 104 | 105 | ### UI Tests (XCTest) 106 | UI tests were introduced in XCode 7. It is native solution which allows us to write UI tests using `Objective-C` or `Swift`. 107 | Tests are located in `ContactsUITests` folder. 108 | 109 | **Run manually:** 110 | ```bash 111 | xcodebuild -project Contacts.xcodeproj \ 112 | -scheme "Contacts" \ 113 | -sdk iphonesimulator \ 114 | -destination 'platform=iOS Simulator,name=iPhone 6,OS=9.1' \ 115 | test 116 | ``` 117 | 118 | **Run with `fastlane`:** 119 | ```bash 120 | fastlane test_xctests 121 | ``` 122 | **Note:** UI tests can be run directly from Xcode: `Product -> Test` 123 | 124 | ### Appium 125 | Appium is an open source test automation framework for use with native, hybrid and mobile web apps. It drives iOS and Android apps using the WebDriver protocol. Appium supports client libraries for multiple programming languages `Java`, `Python`, `Ruby`, `JavaScript`, `PHP` and `C#`. Tests were written in `Ruby` and are located in `appium` folder. 126 | 127 | **Run manually:** 128 | ```bash 129 | xcodebuild -project Contacts.xcodeproj \ 130 | -scheme "Contacts" \ 131 | -sdk iphonesimulator \ 132 | -derivedDataPath "build" \ 133 | build 134 | 135 | cd appium && bundle exec cucumber 136 | ``` 137 | 138 | **Run with fastlane:** 139 | ```bash 140 | fastlane test_appium 141 | ``` 142 | **Note:** Appium server should be running while running tests. 143 | 144 | ### Calabash 145 | Calabash is an automated testing technology for Android and iOS native and hybrid applications. It is a free-to-use open source project that is developed and maintained by Xamarin. Calabash has two client libraries for `Ruby` and `Java`. Tests were written in `Ruby` and are located in `calabash` folder. 146 | 147 | **Run manually:** 148 | ```bash 149 | xcodebuild -project Contacts.xcodeproj \ 150 | -scheme "Contacts-cal" \ 151 | -sdk iphonesimulator \ 152 | -derivedDataPath "build" \ 153 | build 154 | 155 | cd calabash && APP="../Build/Products/Debug-iphonesimulator/Contacts-cal.app" bundle exec cucumber 156 | ``` 157 | 158 | **Run with fastlane:** 159 | ```bash 160 | fastlane test_calabash 161 | ``` 162 | **Note:** Before running calabash tests disable firewall otherwise the prompt below will appear on every simulator run: 163 | ![Contact List](/assets/calabash-firewall.png) 164 | 165 | ### UI Automation 166 | UI Automation is an old native solution which allows us to write UI tests using `JavaScript`. 167 | Tests are located in `ui-automation` folder. 168 | 169 | **Run manually:** 170 | ```bash 171 | xcodebuild -project Contacts.xcodeproj \ 172 | -scheme "Contacts" \ 173 | -sdk iphonesimulator \ 174 | -derivedDataPath "build" \ 175 | build 176 | 177 | cd ui-automation && ./run-tests.sh "../build/Products/Debug-iphonesimulator/Contacts-test.app" "iPhone 6 (9.1)" 178 | ``` 179 | 180 | **Run with fastlane:** 181 | ```bash 182 | fastlane test_ui_automation 183 | ``` 184 | **Note:** Consider UI Tests (XCTest) solution instead of UI Automation as Apple has officially deprecated UI Automation in Xcode 7. 185 | 186 | ## Benchmarks 187 | The `benchmarks` folder contains some scripts to measure the tests running time for all covered solutions. 188 | Some results taken on MacBook Air (Late 2014): 189 | 190 | UI Tests (XCTest) | Appium | Calabash | UI Automation 191 | ------------ | ------------- | ------------- | ------------- 192 | 54s | 3m 53s | 1m 59s | 45s 193 | 194 | 195 | ## References 196 | 197 | * [Appium Github page](https://github.com/appium/appium); 198 | * [Calabash iOS Github page](https://github.com/calabash/calabash-ios); 199 | * [UI Automation API reference guide](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/); 200 | * [UITests presentation at WWDC 2015](https://developer.apple.com/videos/play/wwdc2015-406/). 201 | 202 | ## License 203 | This project is licensed under the terms of the MIT license. See the LICENSE file. 204 | -------------------------------------------------------------------------------- /appium/appium.txt: -------------------------------------------------------------------------------- 1 | [caps] 2 | platformName = "ios" 3 | app = "../Build/Products/Debug-iphonesimulator/Contacts-test.app" 4 | platformVersion = "9.1" 5 | deviceName = "iPhone 5s" -------------------------------------------------------------------------------- /appium/features/add_contact.feature: -------------------------------------------------------------------------------- 1 | @add_contact 2 | Feature: Create a new contact in the contacts list 3 | In order to interact with a new contact 4 | As I user 5 | I want to add a new contact in my contacts list 6 | 7 | @s1 @add_action 8 | Scenario: User can add a new contact in the contacts list 9 | Given I see the contacts list screen 10 | When I press on "Add" button 11 | Then The screen "Edit Contact Details" appears 12 | And I enter "Jon" in the "first name" textfield 13 | And I enter "Snow" in the "last name" textfield 14 | When I press on "Done" button 15 | Then I see the "Jon Snow" contact in the contact list 16 | 17 | 18 | -------------------------------------------------------------------------------- /appium/features/remove_contact.feature: -------------------------------------------------------------------------------- 1 | @remove_contact 2 | Feature: Remove an existing contact from the contacts list 3 | In order to get rid of annoying contacts 4 | As I user 5 | I want to remove an existing contact from my contacts list 6 | 7 | @s1 @remove_action 8 | Scenario: User can remove an existing contact from the contacts list 9 | Given I see the contacts list screen 10 | Given I see at least one contact in the list 11 | When I press on "Edit" button 12 | And I remove the first contact 13 | Then Contacts list is empty 14 | 15 | 16 | -------------------------------------------------------------------------------- /appium/features/step_definitions/steps.rb: -------------------------------------------------------------------------------- 1 | ############ 2 | # Helpers 3 | ############ 4 | 5 | def keyboard_visible? 6 | keyboard = execute_script "UIATarget.localTarget().frontMostApp().keyboard()" 7 | return keyboard != "" 8 | end 9 | 10 | def keyboard_type_string string 11 | execute_script "UIATarget.localTarget().frontMostApp().keyboard().typeString(\"#{string}\")" 12 | end 13 | 14 | ############ 15 | # Steps 16 | ############ 17 | 18 | Given(/^I see the contacts list screen$/) do 19 | step "The screen \"Contacts List\" appears" 20 | end 21 | 22 | When(/^I press on "([^"]*)" button$/) do |arg1| 23 | wait { button(arg1).click } 24 | end 25 | 26 | Then(/^The screen "([^"]*)" appears$/) do |screen| 27 | case screen 28 | when "Edit Contact Details" 29 | wait { find("com.vc.contactsEdit") } 30 | when "Contacts List" 31 | wait { find("com.vc.contactsList") } 32 | when "Contact Details" 33 | wait { find("com.vc.contactDetails") } 34 | end 35 | end 36 | 37 | Then(/^I enter "([^"]*)" in the "([^"]*)" textfield$/) do |arg1, text_field_name| 38 | 39 | text_field_id = nil 40 | case text_field_name 41 | when "first name" 42 | text_field_id = "com.textfield.firstName" 43 | when "last name" 44 | text_field_id = "com.textfield.lastName" 45 | end 46 | 47 | text_field = nil 48 | wait { text_field = textfield(text_field_id) } 49 | set_immediate_value(text_field, "") 50 | text_field.click 51 | wait_true { keyboard_visible?() } 52 | keyboard_type_string(arg1) 53 | end 54 | 55 | Then(/^I see the "([^"]*)" contact in the contact list$/) do |arg1| 56 | cell = nil 57 | wait { cell = find_ele_by_attr("UIATableCell", "label", arg1) } 58 | expect(cell.name()).to eq("com.cell.contactsList") 59 | end 60 | 61 | Given(/^I see at least one contact in the list$/) do 62 | step "I press on \"Add\" button" 63 | step "I enter \"Jon\" in the \"first name\" textfield" 64 | step "I enter \"Snow\" in the \"last name\" textfield" 65 | step "I press on \"Done\" button" 66 | expect(tags("UIATableCell").length()).to eq(1) 67 | end 68 | 69 | When(/^I press on first contact$/) do 70 | wait { tags("UIATableCell")[0].click } 71 | end 72 | 73 | Then(/^I see the "([^"]*)" contact in the contact details$/) do |arg1| 74 | label = nil 75 | wait { label = find_ele_by_attr("UIAStaticText", "label", arg1) } 76 | expect(label.name()).to eq("com.label.contactDetails") 77 | end 78 | 79 | When(/^I remove the first contact$/) do 80 | wait { find_eles_by_attr("UIAButton", "hint", "Double tap to delete item")[0].click } 81 | wait { button("Delete").click } 82 | end 83 | 84 | Then(/^Contacts list is empty$/) do 85 | expect(tags("UIATableCell").length()).to eq(0) 86 | end -------------------------------------------------------------------------------- /appium/features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | require 'appium_lib' 3 | require 'cucumber/ast' 4 | 5 | # Create a custom World class so we don't pollute `Object` with Appium methods 6 | class AppiumWorld 7 | end 8 | 9 | # Load capabilities from appium.txt file used also for appium-console (arc) 10 | caps = Appium.load_appium_txt file: File.expand_path('../../', __FILE__), verbose: true 11 | 12 | Appium::Driver.new(caps) 13 | Appium.promote_appium_methods AppiumWorld 14 | 15 | World do 16 | AppiumWorld.new 17 | end 18 | 19 | Before { $driver.start_driver } 20 | After { $driver.driver_quit } -------------------------------------------------------------------------------- /appium/features/update_contact.feature: -------------------------------------------------------------------------------- 1 | @update_contact 2 | Feature: Update an existing contact in the contacts list 3 | In order to keep all my contacts up-to-date 4 | As I user 5 | I want to update an existing contact in my contacts list 6 | 7 | @s1 @update_action 8 | Scenario: User can update an existing contact in the contacts list 9 | Given I see the contacts list screen 10 | Given I see at least one contact in the list 11 | When I press on first contact 12 | Then The screen "Contact Details" appears 13 | When I press on "Edit" button 14 | Then The screen "Edit Contact Details" appears 15 | And I enter "Robb" in the "first name" textfield 16 | And I enter "Stark" in the "last name" textfield 17 | When I press on "Done" button 18 | Then The screen "Contact Details" appears 19 | And I see the "Robb Stark" contact in the contact details 20 | When I press on "Back" button 21 | Then I see the "Robb Stark" contact in the contact list 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/appium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/appium.png -------------------------------------------------------------------------------- /assets/calabash-firewall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/calabash-firewall.png -------------------------------------------------------------------------------- /assets/contact-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/contact-details.png -------------------------------------------------------------------------------- /assets/contact-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/contact-edit.png -------------------------------------------------------------------------------- /assets/contact-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/contact-list.png -------------------------------------------------------------------------------- /assets/contact-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/assets/contact-remove.png -------------------------------------------------------------------------------- /benchmarks/benchmark-appium.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | START=$(date +%s) 4 | 5 | xcodebuild -project "../Contacts.xcodeproj" \ 6 | -scheme "Contacts" \ 7 | -sdk iphonesimulator \ 8 | -derivedDataPath "../build" \ 9 | build 10 | 11 | cd "../appium" && bundle exec cucumber 12 | 13 | END=$(date +%s) 14 | DIFF=$(echo "$END - $START" | bc) 15 | 16 | echo "Time elapsed: $DIFF" -------------------------------------------------------------------------------- /benchmarks/benchmark-calabash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | START=$(date +%s) 4 | 5 | xcodebuild -project "../Contacts.xcodeproj" \ 6 | -scheme "Contacts-cal" \ 7 | -sdk iphonesimulator \ 8 | -derivedDataPath "../build" \ 9 | build 10 | 11 | cd "../calabash" && APP="../Build/Products/Debug-iphonesimulator/Contacts-cal.app" bundle exec cucumber 12 | 13 | END=$(date +%s) 14 | DIFF=$(echo "$END - $START" | bc) 15 | 16 | echo "Time elapsed: $DIFF" -------------------------------------------------------------------------------- /benchmarks/benchmark-ui-automation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | START=$(date +%s) 4 | 5 | xcodebuild -project "../Contacts.xcodeproj" \ 6 | -scheme "Contacts" \ 7 | -sdk iphonesimulator \ 8 | -derivedDataPath "../build" \ 9 | build 10 | 11 | cd "../ui-automation" && ./run-tests.sh "../build/Products/Debug-iphonesimulator/Contacts-test.app" "iPhone 5s (9.1)" 12 | 13 | END=$(date +%s) 14 | DIFF=$(echo "$END - $START" | bc) 15 | 16 | echo "Time elapsed: $DIFF" -------------------------------------------------------------------------------- /benchmarks/benchmark-xctests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | START=$(date +%s) 4 | 5 | xcodebuild -project "../Contacts.xcodeproj" \ 6 | -scheme "Contacts" \ 7 | -sdk iphonesimulator \ 8 | -derivedDataPath "../build" \ 9 | -destination 'platform=iOS Simulator,name=iPhone 6,OS=9.1' \ 10 | test 11 | 12 | END=$(date +%s) 13 | DIFF=$(echo "$END - $START" | bc) 14 | 15 | echo "Time elapsed: $DIFF" -------------------------------------------------------------------------------- /calabash/calabash.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /calabash/calabash.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/0.16.4: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/CalabashServer.h: -------------------------------------------------------------------------------- 1 | // Created by Karl Krukow on 11/08/11. 2 | // Copyright 2011 LessPainful. All rights reserved. 3 | 4 | #import 5 | 6 | @class LPHTTPServer; 7 | 8 | @interface CalabashServer : NSObject { 9 | LPHTTPServer *_httpServer; 10 | } 11 | 12 | + (void) start; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPCORSResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPCORSResponse.h 3 | // LPSimpleExample 4 | // 5 | // Created by Karl Krukow on 3/3/14. 6 | // Copyright (c) 2014 Xamarin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPHTTPResponse.h" 11 | #import "LPHTTPDataResponse.h" 12 | 13 | 14 | @interface LPCORSResponse : LPHTTPDataResponse 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPHTTPAsyncFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LPHTTPResponse.h" 3 | 4 | @class LPHTTPConnection; 5 | 6 | /** 7 | * This is an asynchronous version of LPHTTPFileResponse. 8 | * It reads data from the given file asynchronously via GCD. 9 | * 10 | * It may be overridden to allow custom post-processing of the data that has been read from the file. 11 | * An example of this is the HTTPDynamicFileResponse class. 12 | **/ 13 | 14 | @interface LPHTTPAsyncFileResponse : NSObject { 15 | LPHTTPConnection *connection; 16 | 17 | NSString *filePath; 18 | UInt64 fileLength; 19 | UInt64 fileOffset; // File offset as pertains to data given to connection 20 | UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) 21 | 22 | BOOL aborted; 23 | 24 | NSData *data; 25 | 26 | int fileFD; 27 | void *readBuffer; 28 | NSUInteger readBufferSize; // Malloced size of readBuffer 29 | NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is 30 | NSUInteger readRequestLength; 31 | dispatch_queue_t readQueue; 32 | dispatch_source_t readSource; 33 | BOOL readSourceSuspended; 34 | } 35 | 36 | - (id)initWithFilePath:(NSString *)filePath forConnection:(LPHTTPConnection *)connection; 37 | 38 | - (NSString *)filePath; 39 | 40 | @end 41 | 42 | /** 43 | * Explanation of Variables (excluding those that are obvious) 44 | * 45 | * fileOffset 46 | * This is the number of bytes that have been returned to the connection via the readDataOfLength method. 47 | * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, 48 | * then the fileOffset variable remains at zero. 49 | * This variable is used in the calculation of the isDone method. 50 | * Only after all data has been returned to the connection are we actually done. 51 | * 52 | * readOffset 53 | * Represents the offset of the file descriptor. 54 | * In other words, the file position indicator for our read stream. 55 | * It might be easy to think of it as the total number of bytes that have been read from the file. 56 | * However, this isn't entirely accurate, as the setOffset: method may have caused us to 57 | * jump ahead in the file (lseek). 58 | * 59 | * readBuffer 60 | * Malloc'd buffer to hold data read from the file. 61 | * 62 | * readBufferSize 63 | * Total allocation size of malloc'd buffer. 64 | * 65 | * readBufferOffset 66 | * Represents the position in the readBuffer where we should store new bytes. 67 | * 68 | * readRequestLength 69 | * The total number of bytes that were requested from the connection. 70 | * It's OK if we return a lesser number of bytes to the connection. 71 | * It's NOT OK if we return a greater number of bytes to the connection. 72 | * Doing so would disrupt proper support for range requests. 73 | * If, however, the response is chunked then we don't need to worry about this. 74 | * Chunked responses inheritly don't support range requests. 75 | **/ 76 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPHTTPDataResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LPHTTPResponse.h" 3 | 4 | 5 | @interface LPHTTPDataResponse : NSObject { 6 | NSUInteger offset; 7 | NSData *data; 8 | } 9 | 10 | - (id)initWithData:(NSData *)data; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPHTTPDynamicFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LPHTTPResponse.h" 3 | #import "LPHTTPAsyncFileResponse.h" 4 | 5 | @class LPHTTPConnection; 6 | /** 7 | * This class is designed to assist with dynamic content. 8 | * Imagine you have a file that you want to make dynamic: 9 | * 10 | * 11 | * 12 | *

ComputerName Control Panel

13 | * ... 14 | *
  • System Time: SysTime
  • 15 | * 16 | * 17 | * 18 | * Now you could generate the entire file in Objective-C, 19 | * but this would be a horribly tedious process. 20 | * Beside, you want to design the file with professional tools to make it look pretty. 21 | * 22 | * So all you have to do is escape your dynamic content like this: 23 | * 24 | * ... 25 | *

    %%ComputerName%% Control Panel

    26 | * ... 27 | *
  • System Time: %%SysTime%%
  • 28 | * 29 | * And then you create an instance of this class with: 30 | * 31 | * - separator = @"%%" 32 | * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } 33 | * 34 | * This class will then perform the replacements for you, on the fly, as it reads the file data. 35 | * This class is also asynchronous, so it will perform the file IO using its own GCD queue. 36 | * 37 | * All keys for the replacementDictionary must be NSString's. 38 | * Values for the replacementDictionary may be NSString's, or any object that 39 | * returns what you want when its description method is invoked. 40 | **/ 41 | 42 | @interface LPHTTPDynamicFileResponse : LPHTTPAsyncFileResponse { 43 | NSData *separator; 44 | NSDictionary *replacementDict; 45 | } 46 | 47 | - (id)initWithFilePath:(NSString *)filePath 48 | forConnection:(LPHTTPConnection *)connection 49 | separator:(NSString *)separatorStr 50 | replacementDictionary:(NSDictionary *)dictionary; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPHTTPFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LPHTTPResponse.h" 3 | 4 | @class LPHTTPConnection; 5 | 6 | 7 | @interface LPHTTPFileResponse : NSObject { 8 | LPHTTPConnection *connection; 9 | 10 | NSString *filePath; 11 | UInt64 fileLength; 12 | UInt64 fileOffset; 13 | 14 | BOOL aborted; 15 | 16 | int fileFD; 17 | void *buffer; 18 | NSUInteger bufferSize; 19 | } 20 | 21 | - (id)initWithFilePath:(NSString *)filePath forConnection:(LPHTTPConnection *)connection; 22 | 23 | - (NSString *)filePath; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPHTTPResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @protocol LPHTTPResponse 5 | 6 | /** Returns the length of the data in bytes. If you don't know the length in 7 | * advance, implement the isChunked method and have it return YES. 8 | **/ 9 | - (UInt64)contentLength; 10 | 11 | /** The HTTP server supports range requests in order to allow things like file 12 | * download resumption and optimized streaming on mobile devices. 13 | **/ 14 | - (UInt64)offset; 15 | 16 | - (void)setOffset:(UInt64)offset; 17 | 18 | /** Returns the data for the response. You do not have to return data of the 19 | * exact length that is given. You may optionally return data of a lesser 20 | * length. However, you must never return data of a greater length than 21 | * requested. Doing so could disrupt proper support for range requests. 22 | * 23 | * To support asynchronous responses, read the discussion at the bottom of this 24 | * header. 25 | **/ 26 | - (NSData *)readDataOfLength:(NSUInteger)length; 27 | 28 | /** Should only return YES after the HTTPConnection has read all available data. 29 | * That is, all data for the response has been returned to the HTTPConnection 30 | * via the readDataOfLength method. 31 | **/ 32 | - (BOOL)isDone; 33 | 34 | @optional 35 | 36 | /** If you need time to calculate any part of the HTTP response headers (status 37 | * code or header fields), this method allows you to delay sending the headers 38 | * so that you may asynchronously execute the calculations. Simply implement 39 | * this method and return YES until you have everything you need concerning the 40 | * headers. 41 | * 42 | * This method ties into the asynchronous response architecture of the 43 | * HTTPConnection. You should read the full discussion at the bottom of this 44 | * header. 45 | * 46 | * If you return YES from this method, the HTTPConnection will wait for you to 47 | * invoke the responseHasAvailableData method. After you do, the HTTPConnection 48 | * will again invoke this method to see if the response is ready to send the 49 | * headers. 50 | * 51 | * You should only delay sending the headers until you have everything you need 52 | * concerning just the headers. Asynchronously generating the body of the 53 | * response is not an excuse to delay sending the headers. Instead you should 54 | * tie into the asynchronous response architecture, and use techniques such as 55 | * the isChunked method. 56 | * 57 | * Important: You should read the discussion at the bottom of this header. 58 | **/ 59 | - (BOOL)delayResponseHeaders; 60 | 61 | /** Status code for response. Allows for responses such as redirect (301), etc. 62 | **/ 63 | - (NSInteger)status; 64 | 65 | /** If you want to add any extra HTTP headers to the response, simply return 66 | * them in a dictionary in this method. 67 | **/ 68 | - (NSDictionary *)httpHeaders; 69 | 70 | /** If you don't know the content-length in advance, implement this method in 71 | * your custom response class and return YES. 72 | * 73 | * Important: You should read the discussion at the bottom of this header. 74 | **/ 75 | - (BOOL)isChunked; 76 | 77 | /** This method is called from the HTTPConnection class when the connection is 78 | * closed, or when the connection is finished with the response. If your 79 | * response is asynchronous, you should implement this method so you know not to 80 | * invoke any methods on the HTTPConnection after this method is called (as the 81 | * connection may be deallocated). 82 | **/ 83 | - (void)connectionDidClose; 84 | 85 | @end 86 | 87 | 88 | /** Important notice to those implementing custom asynchronous and/or chunked 89 | * responses: 90 | * 91 | * HTTPConnection supports asynchronous responses. All you have to do in your 92 | * custom response class is asynchronously generate the response, and invoke 93 | * HTTPConnection's responseHasAvailableData method. You don't have to wait 94 | * until you have all of the response ready to invoke this method. For example, 95 | * if you generate the response in incremental chunks, you could call 96 | * responseHasAvailableData after generating each chunk. Please see the 97 | * HTTPAsyncFileResponse class for an example of how to do this. 98 | * 99 | * The normal flow of events for an HTTPConnection while responding to a request 100 | * is like this: - Send http response headers - Get data from response via 101 | * readDataOfLength method. - Add data to asyncSocket's write queue. - Wait 102 | * for asyncSocket to notify it that the data has been sent. - Get more data 103 | * from response via readDataOfLength method. - ... continue this cycle until 104 | * the entire response has been sent. 105 | * 106 | * With an asynchronous response, the flow is a little different. 107 | * 108 | * First the LPHTTPResponse is given the opportunity to postpone sending the 109 | * HTTP response headers. This allows the response to asynchronously execute 110 | * any code needed to calculate a part of the header. An example might be the 111 | * response needs to generate some custom header fields, or perhaps the response 112 | * needs to look for a resource on network-attached storage. Since the 113 | * network-attached storage may be slow, the response doesn't know whether to 114 | * send a 200 or 404 yet. In situations such as this, the LPHTTPResponse simply 115 | * implements the delayResponseHeaders method and returns YES. After returning 116 | * YES from this method, the HTTPConnection will wait until the response invokes 117 | * its responseHasAvailableData method. After this occurs, the HTTPConnection 118 | * will again query the delayResponseHeaders method to see if the response is 119 | * ready to send the headers. This cycle will continue until the 120 | * delayResponseHeaders method returns NO. 121 | * 122 | * You should only delay sending the response headers until you have everything 123 | * you need concerning just the headers. Asynchronously generating the body of 124 | * the response is not an excuse to delay sending the headers. 125 | * 126 | * After the response headers have been sent, the HTTPConnection calls your 127 | * readDataOfLength method. You may or may not have any available data at this 128 | * point. If you don't, then simply return nil. You should later invoke 129 | * HTTPConnection's responseHasAvailableData when you have data to send. 130 | * 131 | * You don't have to keep track of when you return nil in the readDataOfLength 132 | * method, or how many times you've invoked responseHasAvailableData. Just 133 | * simply call responseHasAvailableData whenever you've generated new data, and 134 | * return nil in your readDataOfLength whenever you don't have any available 135 | * data in the requested range. HTTPConnection will automatically detect when 136 | * it should be requesting new data and will act appropriately. 137 | * 138 | * It's important that you also keep in mind that the HTTP server supports range 139 | * requests. The setOffset method is mandatory, and should not be ignored. 140 | * Make sure you take into account the offset within the readDataOfLength 141 | * method. You should also be aware that the HTTPConnection automatically sorts 142 | * any range requests. So if your setOffset method is called with a value of 143 | * 100, then you can safely release bytes 0-99. 144 | * 145 | * HTTPConnection can also help you keep your memory footprint small. Imagine 146 | * you're dynamically generating a 10 MB response. You probably don't want to 147 | * load all this data into RAM, and sit around waiting for HTTPConnection to 148 | * slowly send it out over the network. All you need to do is pay attention to 149 | * when HTTPConnection requests more data via readDataOfLength. This is because 150 | * HTTPConnection will never allow asyncSocket's write queue to get much bigger 151 | * than READ_CHUNKSIZE bytes. You should consider how you might be able to take 152 | * advantage of this fact to generate your asynchronous response on demand, 153 | * while at the same time keeping your memory footprint small, and your 154 | * application lightning fast. 155 | * 156 | * If you don't know the content-length in advanced, you should also implement 157 | * the isChunked method. This means the response will not include a 158 | * Content-Length header, and will instead use "Transfer-Encoding: chunked". 159 | * There's a good chance that if your response is asynchronous and dynamic, it's 160 | * also chunked. If your response is chunked, you don't need to worry about 161 | * range requests. 162 | **/ 163 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPIsWebView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface LPIsWebView : NSObject 4 | 5 | + (BOOL) isWebView:(id) object; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPRoute.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPRoute.h 3 | // 4 | // Created by Karl Krukow on 13/08/11. 5 | // Copyright 2011 LessPainful. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @class LPHTTPConnection; 11 | 12 | @protocol LPRoute 13 | 14 | @optional 15 | - (void) setParameters:(NSDictionary *) parameters; 16 | 17 | - (void) setConnection:(LPHTTPConnection *) connection; 18 | 19 | - (BOOL) supportsMethod:(NSString *) method atPath:(NSString *) path; 20 | 21 | - (NSDictionary *) JSONResponseForMethod:(NSString *) method 22 | URI:(NSString *) path 23 | data:(NSDictionary *) data; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPRouter.h 3 | // Created by Karl Krukow on 13/08/11. 4 | // Copyright 2011 LessPainful. All rights reserved. 5 | 6 | #import "LPHTTPConnection.h" 7 | #import "LPRoute.h" 8 | 9 | @interface LPRouter : LPHTTPConnection 10 | 11 | - (NSData *) postData; 12 | 13 | + (void) addRoute:(id ) route forPath:(NSString *) path; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPVersionRoute.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPVersionRoute.h 3 | // calabash 4 | // 5 | // Created by Karl Krukow on 22/06/12. 6 | // Copyright (c) 2012 LessPainful. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPRoute.h" 11 | 12 | /*** UNEXPECTED *** 13 | We have tools that search the strings in the compiled binary for a match on 14 | 'CALABASH VERSION'. 15 | 16 | eg. $ strings Briar-cal.app/Briar-cal | grep -E 'CALABASH VERSION' 17 | 18 | We use this information to determine the version of the server that is compiled 19 | into binary. 20 | 21 | Do not change the 'CALABASH VERSION' portion of the following constant without 22 | updating the ruby API. 23 | ******************/ 24 | #define kLPCALABASHVERSION @"CALABASH VERSION: 0.16.4" 25 | 26 | @interface LPVersionRoute : NSObject 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPWebQuery.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPWebQuery.h 3 | // CalabashJS 4 | // 5 | // Created by Karl Krukow on 27/06/12. 6 | // Copyright (c) 2012 Xamarin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "LPWebViewProtocol.h" 12 | #import "UIWebView+LPWebView.h" 13 | 14 | static NSString *LP_QUERY_JS = @"(function(){function isHostMethod(object,property){var t=typeof object[property];return t==='function'||(!!(t==='object'&&object[property]))||t==='unknown';}var NODE_TYPES={1:'ELEMENT_NODE',2:'ATTRIBUTE_NODE',3:'TEXT_NODE',9:'DOCUMENT_NODE'};function boundingClientRect(object){var rect=null,jsonRect=null;if(isHostMethod(object,'getBoundingClientRect')){rect=object.getBoundingClientRect(),jsonRect={left:rect.left,top:rect.top,width:rect.width,height:rect.height,x:rect.left+Math.floor(rect.width/2),y:rect.top+Math.floor(rect.height/2)};}return jsonRect;}function computeRectForNode(object,fullDump){var res={};res.rect=boundingClientRect(object);res.nodeType=NODE_TYPES[object.nodeType]||res.nodeType+' (Unexpected)';res.nodeName=object.nodeName;res.id=object.id||'';res['class']=object.className||'';if(object.href){res.href=object.href;}if(object.hasOwnProperty('value')){res.value=object.value||'';}if(fullDump||object.nodeType==3){res.textContent=object.textContent;}return res;}function toJSON(object,fullDump){var res,i,N,spanEl,parentEl;if(typeof object==='undefined'){throw {message:'Calling toJSON with undefined'};}else{if(object instanceof Text){parentEl=object.parentElement;if(parentEl){spanEl=document.createElement('calabash');spanEl.style.display='inline';spanEl.innerHTML=object.textContent;parentEl.replaceChild(spanEl,object);res=computeRectForNode(spanEl,fullDump);res.nodeType=NODE_TYPES[object.nodeType];res.textContent=object.textContent;delete res.nodeName;delete res.id;delete res['class'];parentEl.replaceChild(object,spanEl);}else{res=object;}}else{if(object instanceof Node){res=computeRectForNode(object,fullDump);}else{if(object instanceof NodeList||(typeof object=='object'&&object&&typeof object.length==='number'&&object.length>0&&typeof object[0]!=='undefined')){res=[];for(i=0,N=object.length;i *) webView 28 | includeInvisible:(BOOL) includeInvisible; 29 | 30 | + (NSDictionary *) dictionaryOfViewsInWebView:(UIView *) webView; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/LPWebViewProtocol.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol LPWebViewProtocol 4 | 5 | @required 6 | 7 | // The bridge between UIWebView and WKWebKit. 8 | - (NSString *) calabashStringByEvaluatingJavaScript:(NSString *) javascript; 9 | 10 | - (BOOL) pointInside:(CGPoint) point withEvent:(UIEvent *) event; 11 | - (UIScrollView *) scrollView; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Headers/UIWebView+LPWebView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "LPWebViewProtocol.h" 3 | 4 | @interface UIWebView (UIWebView_LPWebView) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/Resources/version: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/calabash/calabash.framework/Versions/A/Resources/version -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/A/calabash: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/calabash/calabash.framework/Versions/A/calabash -------------------------------------------------------------------------------- /calabash/calabash.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /calabash/calabash.framework/calabash: -------------------------------------------------------------------------------- 1 | Versions/Current/calabash -------------------------------------------------------------------------------- /calabash/features/add_contact.feature: -------------------------------------------------------------------------------- 1 | @add_contact 2 | Feature: Create a new contact in the contacts list 3 | In order to interact with a new contact 4 | As I user 5 | I want to add a new contact in my contacts list 6 | 7 | @s1 @add_action 8 | Scenario: User can add a new contact in the contacts list 9 | Given I see the contacts list screen 10 | When I press on "Add" button 11 | Then The screen "Edit Contact Details" appears 12 | And I enter "Jon" in the "first name" textfield 13 | And I enter "Snow" in the "last name" textfield 14 | When I press on "Done" button 15 | Then I see the "Jon Snow" contact in the contact list 16 | 17 | 18 | -------------------------------------------------------------------------------- /calabash/features/remove_contact.feature: -------------------------------------------------------------------------------- 1 | @remove_contact 2 | Feature: Remove an existing contact from the contacts list 3 | In order to get rid of annoying contacts 4 | As I user 5 | I want to remove an existing contact from my contacts list 6 | 7 | @s1 @remove_action 8 | Scenario: User can remove an existing contact from the contacts list 9 | Given I see the contacts list screen 10 | Given I see at least one contact in the list 11 | When I press on "Edit" button 12 | And I remove the first contact 13 | Then Contacts list is empty 14 | 15 | 16 | -------------------------------------------------------------------------------- /calabash/features/step_definitions/calabash_steps.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-cucumber/calabash_steps' -------------------------------------------------------------------------------- /calabash/features/step_definitions/steps.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-cucumber/calabash_steps' 2 | 3 | ############ 4 | # Steps 5 | ############ 6 | 7 | Given(/^I see the contacts list screen$/) do 8 | step "The screen \"Contacts List\" appears" 9 | end 10 | 11 | When(/^I press on "([^"]*)" button$/) do |arg1| 12 | if arg1 == "Back" 13 | step "I go back" 14 | else 15 | step "I touch the \"#{arg1}\" button" 16 | end 17 | end 18 | 19 | Then(/^The screen "([^"]*)" appears$/) do |screen| 20 | case screen 21 | when "Edit Contact Details" 22 | wait_for { not query("UITableView accessibilityIdentifier:'com.vc.contactsEdit'").empty? } 23 | when "Contacts List" 24 | wait_for { not query("UITableView accessibilityIdentifier:'com.vc.contactsList'").empty? } 25 | when "Contact Details" 26 | wait_for { not query("UIView accessibilityIdentifier:'com.vc.contactDetails'").empty? } 27 | end 28 | end 29 | 30 | Then(/^I enter "([^"]*)" in the "([^"]*)" textfield$/) do |arg1, text_field_name| 31 | 32 | text_field_id = nil 33 | case text_field_name 34 | when "first name" 35 | text_field_id = "com.textfield.firstName" 36 | when "last name" 37 | text_field_id = "com.textfield.lastName" 38 | end 39 | 40 | #step "I clear \"#{text_field_id}\"" 41 | clear_text("textField marked: '#{text_field_id}'") 42 | step "I fill in \"#{text_field_id}\" with \"#{arg1}\"" 43 | end 44 | 45 | Then(/^I see the "([^"]*)" contact in the contact list$/) do |arg1| 46 | step "I should see \"#{arg1}\"" 47 | expect(query("UITableViewCell accessibilityIdentifier:'com.cell.contactsList'").empty?).to be false 48 | end 49 | 50 | Given(/^I see at least one contact in the list$/) do 51 | step "I press on \"Add\" button" 52 | step "I enter \"Jon\" in the \"first name\" textfield" 53 | step "I enter \"Snow\" in the \"last name\" textfield" 54 | step "I press on \"Done\" button" 55 | expect(query("tableView", numberOfRowsInSection:0)[0]).to eq(1) 56 | end 57 | 58 | When(/^I press on first contact$/) do 59 | step "I touch \"com.cell.contactsList\"" 60 | end 61 | 62 | Then(/^I see the "([^"]*)" contact in the contact details$/) do |arg1| 63 | step "I should see \"#{arg1}\"" 64 | expect(query("UILabel accessibilityIdentifier:'com.label.contactDetails'").empty?).to be false 65 | end 66 | 67 | When(/^I remove the first contact$/) do 68 | touch("tableViewCell index:0 descendant tableViewCellEditControl") 69 | step "I touch the \"Delete\" button" 70 | end 71 | 72 | Then(/^Contacts list is empty$/) do 73 | expect(query("tableView", numberOfRowsInSection:0)[0]).to eq(0) 74 | end -------------------------------------------------------------------------------- /calabash/features/support/01_launch.rb: -------------------------------------------------------------------------------- 1 | ######################################## 2 | # # 3 | # Important Note # 4 | # # 5 | # When running calabash-ios tests at # 6 | # www.xamarin.com/test-cloud # 7 | # the methods invoked by # 8 | # CalabashLauncher are overriden. # 9 | # It will automatically ensure # 10 | # running on device, installing apps # 11 | # etc. # 12 | # # 13 | ######################################## 14 | 15 | require 'calabash-cucumber/launcher' 16 | 17 | #APP_BUNDLE_PATH = "path/to/app.app" 18 | 19 | Before do |scenario| 20 | @calabash_launcher = Calabash::Cucumber::Launcher.new 21 | unless @calabash_launcher.calabash_no_launch? 22 | @calabash_launcher.relaunch 23 | @calabash_launcher.calabash_notify(self) 24 | end 25 | end 26 | 27 | After do |scenario| 28 | unless @calabash_launcher.calabash_no_stop? 29 | calabash_exit 30 | if @calabash_launcher.active? 31 | @calabash_launcher.stop 32 | end 33 | end 34 | end 35 | 36 | at_exit do 37 | launcher = Calabash::Cucumber::Launcher.new 38 | if launcher.simulator_target? 39 | launcher.simulator_launcher.stop unless launcher.calabash_no_stop? 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /calabash/features/support/02_pre_stop_hooks.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/calabash/features/support/02_pre_stop_hooks.rb -------------------------------------------------------------------------------- /calabash/features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-cucumber/cucumber' 2 | -------------------------------------------------------------------------------- /calabash/features/update_contact.feature: -------------------------------------------------------------------------------- 1 | @update_contact 2 | Feature: Update an existing contact in the contacts list 3 | In order to keep all my contacts up-to-date 4 | As I user 5 | I want to update an existing contact in my contacts list 6 | 7 | @s1 @update_action 8 | Scenario: User can update an existing contact in the contacts list 9 | Given I see the contacts list screen 10 | Given I see at least one contact in the list 11 | When I press on first contact 12 | Then The screen "Contact Details" appears 13 | When I press on "Edit" button 14 | Then The screen "Edit Contact Details" appears 15 | And I enter "Robb" in the "first name" textfield 16 | And I enter "Stark" in the "last name" textfield 17 | When I press on "Done" button 18 | Then The screen "Contact Details" appears 19 | And I see the "Robb Stark" contact in the contact details 20 | When I press on "Back" button 21 | Then I see the "Robb Stark" contact in the contact list 22 | 23 | 24 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | fastlane_version "1.36.4" 2 | 3 | default_platform :ios 4 | 5 | platform :ios do 6 | before_all do 7 | end 8 | 9 | lane :build do |params| 10 | xcodebuild( 11 | scheme: params[:scheme], 12 | build: true, 13 | sdk: "iphonesimulator", 14 | derivedDataPath: "build", 15 | xcargs: { 16 | ONLY_ACTIVE_ARCH: "NO" 17 | } 18 | ) 19 | end 20 | 21 | lane :test_appium do 22 | build( 23 | scheme: "Contacts" 24 | ) 25 | 26 | sh "cd ../appium && bundle exec cucumber" 27 | end 28 | 29 | lane :test_calabash do 30 | build( 31 | scheme: "Contacts-cal" 32 | ) 33 | 34 | sh "cd ../calabash && APP=\"../Build/Products/Debug-iphonesimulator/Contacts-cal.app\" bundle exec cucumber" 35 | end 36 | 37 | lane :test_ui_automation do 38 | build( 39 | scheme: "Contacts" 40 | ) 41 | 42 | sh "cd ../ui-automation && ./run-tests.sh \"../build/Products/Debug-iphonesimulator/Contacts-test.app\" \"iPhone 5s (9.1)\"" 43 | end 44 | 45 | lane :test_xctests do 46 | scan( 47 | scheme: "Contacts", 48 | device: "iPhone 6 (9.1)" 49 | ) 50 | end 51 | 52 | after_all do |lane| 53 | end 54 | 55 | error do |lane, exception| 56 | end 57 | end -------------------------------------------------------------------------------- /ui-automation/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | realpath() { 4 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 5 | } 6 | 7 | XCODE_PATH=`xcode-select -print-path` 8 | TRACETEMPLATE="/Applications/Xcode.app/Contents/Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.xrplugin/Contents/Resources/Automation.tracetemplate" 9 | APP_LOCATION=$(realpath "$1") 10 | DEVICE_ID=$2 11 | 12 | if [ ! $# -gt 1 ]; then 13 | echo "You must specify the app location and the test file." 14 | echo "\t (optionally supply unique device ID of physical iOS device)" 15 | echo "\t eg. ./run-tests.sh suite.js /build/Products/Debug-iphonesimulator/app.app " 16 | exit -1 17 | fi 18 | 19 | # Create junit reporting directory 20 | if [ ! -d "test-reports" ]; then 21 | mkdir test-reports 22 | fi 23 | 24 | # Run all specs 25 | for script in specs/* 26 | do 27 | if [[ -f $script ]]; then 28 | echo $script 29 | instruments -w "$DEVICE_ID" \ 30 | -t $TRACETEMPLATE \ 31 | $APP_LOCATION \ 32 | -e UIASCRIPT $script \ 33 | -e UIARESULTSPATH /var/tmp | grep "<" > test-reports/test-results.xml 34 | 35 | # cleanup the tracefiles produced from instruments 36 | rm -rf *.trace 37 | fi 38 | done 39 | 40 | # fail script if any failures have been generated 41 | if [ `grep "" test-reports/test-results.xml | wc -l` -gt 0 ]; then 42 | echo 'Build Failed' 43 | exit -1 44 | else 45 | echo 'Build Passed' 46 | exit 0 47 | fi 48 | 49 | 50 | -------------------------------------------------------------------------------- /ui-automation/specs/add_contact.js: -------------------------------------------------------------------------------- 1 | var target = UIATarget.localTarget(); 2 | var app = target.frontMostApp(); 3 | 4 | target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT); 5 | app.navigationBar().rightButton().tap(); 6 | app.mainWindow().tableViews()["com.vc.contactsEdit"].cells()["First Name"].textFields()["com.textfield.firstName"].textFields()[0].tap(); 7 | app.keyboard().typeString("Jon\nSnow\n"); 8 | app.navigationBar().rightButton().tap(); 9 | 10 | // TBD: Add asserts -------------------------------------------------------------------------------- /ui-automation/specs/remove_contact.js: -------------------------------------------------------------------------------- 1 | var target = UIATarget.localTarget(); 2 | 3 | target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT); 4 | target.frontMostApp().navigationBar().rightButton().tap(); 5 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsEdit"].cells()["First Name"].textFields()["com.textfield.firstName"].textFields()[0].tap(); 6 | target.frontMostApp().keyboard().typeString("Jon\nSnow"); 7 | target.frontMostApp().keyboard().typeString("\n"); 8 | target.frontMostApp().navigationBar().rightButton().tap(); 9 | target.frontMostApp().navigationBar().leftButton().tap(); 10 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsList"].cells()["com.cell.contactsList"].tap(); 11 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsList"].cells()["com.cell.contactsList"].tap(); 12 | 13 | // TBD: Add asserts -------------------------------------------------------------------------------- /ui-automation/specs/update_contact.js: -------------------------------------------------------------------------------- 1 | var target = UIATarget.localTarget(); 2 | 3 | target.setDeviceOrientation(UIA_DEVICE_ORIENTATION_PORTRAIT); 4 | target.frontMostApp().navigationBar().rightButton().tap(); 5 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsEdit"].cells()["First Name"].textFields()["com.textfield.firstName"].textFields()[0].tap(); 6 | target.frontMostApp().keyboard().typeString("Jon\nSnow\n"); 7 | target.frontMostApp().navigationBar().rightButton().tap(); 8 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsList"].tapWithOptions({tapOffset:{x:0.38, y:0.15}}); 9 | target.frontMostApp().navigationBar().rightButton().tap(); 10 | target.frontMostApp().mainWindow().tableViews()["com.vc.contactsEdit"].cells()["First Name"].textFields()["com.textfield.firstName"].textFields()[0].tap(); 11 | target.frontMostApp().keyboard().keys()["delete"].tapWithOptions({tapCount:3}); 12 | target.frontMostApp().keyboard().typeString("Robb\n"); 13 | target.frontMostApp().keyboard().keys()["delete"].tapWithOptions({tapCount:4}); 14 | target.frontMostApp().keyboard().typeString("Stark\n"); 15 | target.frontMostApp().navigationBar().rightButton().tap(); 16 | target.frontMostApp().navigationBar().leftButton().tap(); 17 | 18 | // TBD: Add asserts -------------------------------------------------------------------------------- /ui-automation/test-reports/test-results.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmx/ios-ui-automation-overview/a7b5a8ae218024d271168c45338d1096695ac2d3/ui-automation/test-reports/test-results.xml --------------------------------------------------------------------------------