├── .gitignore ├── .travis.yml ├── Demo.gif ├── IQUITestCodeMaker.podspec ├── IQUITestCodeMaker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── IQUITestCodeMaker.xcscheme ├── IQUITestCodeMaker.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── IQUITestCodeMaker ├── IQUITestCodeMaker.h ├── IQUITestCodeMakerCapabilities.h ├── IQUITestCodeMakerCapabilities.m ├── IQUITestCodeMakerFactory.h ├── IQUITestCodeMakerFactory.m ├── IQUITestCodeMakerGenerator.h ├── IQUITestCodeMakerGenerator.m ├── IQUITestCodeMakerJSPromiseUnit.h ├── IQUITestCodeMakerJSPromiseUnit.m ├── IQUITestCodeMakerJSWDUnit.h ├── IQUITestCodeMakerJSWDUnit.m ├── IQUITestCodeMakerJavaUnit.h ├── IQUITestCodeMakerJavaUnit.m ├── IQUITestCodeMakerPythonUnit.h ├── IQUITestCodeMakerPythonUnit.m ├── IQUITestCodeMakerRubyUnit.h ├── IQUITestCodeMakerRubyUnit.m ├── IQUITestDebugBall.h ├── IQUITestDebugBall.m ├── IQUITestDebugKeyValueCell.h ├── IQUITestDebugKeyValueCell.m ├── IQUITestDebugKeyValueModel.h ├── IQUITestDebugKeyValueModel.m ├── IQUITestDebugScriptCell.h ├── IQUITestDebugScriptCell.m ├── IQUITestDebugScriptModel.h ├── IQUITestDebugScriptModel.m ├── IQUITestDebugServerInfoCell.h ├── IQUITestDebugServerInfoCell.m ├── IQUITestDebugServerInfoModel.h ├── IQUITestDebugServerInfoModel.m ├── IQUITestDebugSwitchCell.h ├── IQUITestDebugSwitchCell.m ├── IQUITestDebugSwitchModel.h ├── IQUITestDebugSwitchModel.m ├── IQUITestDebugTable.h ├── IQUITestDebugTable.m ├── IQUITestOperationEvent.h ├── IQUITestOperationEvent.m ├── IQUITestProtocol.h ├── UIApplication+IQGlobalHook.h └── UIApplication+IQGlobalHook.m ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── README_CN.md ├── _config.yml └── coordinate.png /.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 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | Pods/ 38 | #Podfile 39 | Podfile.lock 40 | # Carthage 41 | # 42 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 43 | # Carthage/Checkouts 44 | 45 | Carthage/Build 46 | 47 | # fastlane 48 | # 49 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 50 | # screenshots whenever they are needed. 51 | # For more information about the recommended setup visit: 52 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 53 | 54 | fastlane/report.xml 55 | fastlane/Preview.html 56 | fastlane/screenshots/**/*.png 57 | fastlane/test_output 58 | 59 | # Code Injection 60 | # 61 | # After new code Injection tools there's a generated folder /iOSInjectionProject 62 | # https://github.com/johnno1962/injectionforxcode 63 | 64 | iOSInjectionProject/ 65 | .DS_Store 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode9 2 | language: objective-c 3 | notifications: 4 | email: zhiwei.geek@gmail.com 5 | env: 6 | global: 7 | - LANG=en_US.UTF-8 8 | - LC_ALL=en_US.UTF-8 9 | matrix: 10 | - SCHEME="IQUITestCodeMaker" 11 | before_install: 12 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 13 | - gem install cocoapods --pre --no-rdoc --no-ri --no-document --quiet 14 | - pod install --repo-update 15 | script: 16 | - set -o pipefail 17 | - echo Build as static library 18 | - xcodebuild clean build -workspace IQUITestCodeMaker.xcworkspace -scheme 'IQUITestCodeMaker' -sdk iphonesimulator PLATFORM_NAME=iphonesimulator -configuration Debug | xcpretty -c 19 | after_success: 20 | - sleep 5 21 | -------------------------------------------------------------------------------- /Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lobster-King/IQUITestCodeMaker/78c02850f31ea23aad178a68cff083f1418b2b83/Demo.gif -------------------------------------------------------------------------------- /IQUITestCodeMaker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "IQUITestCodeMaker" 3 | s.version = "1.0.1" 4 | s.summary = "IQUITestCodeMaker is a modern and lightweight tool for generating iOS UI test script codes automatically without lanuching Appium Desktop or using inspector." 5 | s.homepage = "https://lobster-king.github.io/IQUITestCodeMaker/" 6 | s.license = "MIT" 7 | s.authors = { 'lobster' => 'zhiwei.geek@gmail.com'} 8 | s.platform = :ios, "8.0" 9 | s.source = { :git => "https://github.com/Lobster-King/IQUITestCodeMaker.git", :tag => s.version } 10 | s.source_files = 'IQUITestCodeMaker', 'IQUITestCodeMaker/**/*.{h,m}' 11 | s.requires_arc = true 12 | s.dependency 'GCDWebServer' 13 | end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AAFFC991FED3AF210F8C4A7E /* libPods-IQUITestCodeMaker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9C52283157AB2941822867D /* libPods-IQUITestCodeMaker.a */; }; 11 | BAC7A41F211D2BA1001DC8F0 /* IQUITestCodeMaker.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = BAC7A41C211D2BA1001DC8F0 /* IQUITestCodeMaker.h */; }; 12 | BAC7A45E211D2C3A001DC8F0 /* IQUITestOperationEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A425211D2C34001DC8F0 /* IQUITestOperationEvent.m */; }; 13 | BAC7A45F211D2C3A001DC8F0 /* IQUITestCodeMakerPythonUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A426211D2C34001DC8F0 /* IQUITestCodeMakerPythonUnit.m */; }; 14 | BAC7A460211D2C3A001DC8F0 /* IQUITestDebugServerInfoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A427211D2C34001DC8F0 /* IQUITestDebugServerInfoModel.m */; }; 15 | BAC7A461211D2C3A001DC8F0 /* IQUITestDebugKeyValueCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A428211D2C34001DC8F0 /* IQUITestDebugKeyValueCell.m */; }; 16 | BAC7A462211D2C3A001DC8F0 /* IQUITestDebugScriptCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A429211D2C34001DC8F0 /* IQUITestDebugScriptCell.m */; }; 17 | BAC7A463211D2C3A001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A42B211D2C34001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.m */; }; 18 | BAC7A464211D2C3A001DC8F0 /* IQUITestDebugScriptModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A42D211D2C34001DC8F0 /* IQUITestDebugScriptModel.m */; }; 19 | BAC7A465211D2C3A001DC8F0 /* IQUITestDebugSwitchModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A42E211D2C34001DC8F0 /* IQUITestDebugSwitchModel.m */; }; 20 | BAC7A466211D2C3A001DC8F0 /* IQUITestDebugKeyValueModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A434211D2C35001DC8F0 /* IQUITestDebugKeyValueModel.m */; }; 21 | BAC7A467211D2C3A001DC8F0 /* IQUITestCodeMakerRubyUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A438211D2C35001DC8F0 /* IQUITestCodeMakerRubyUnit.m */; }; 22 | BAC7A468211D2C3A001DC8F0 /* IQUITestCodeMakerFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A439211D2C35001DC8F0 /* IQUITestCodeMakerFactory.m */; }; 23 | BAC7A469211D2C3A001DC8F0 /* IQUITestDebugBall.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A43A211D2C35001DC8F0 /* IQUITestDebugBall.m */; }; 24 | BAC7A46A211D2C3A001DC8F0 /* IQUITestDebugSwitchCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A43C211D2C35001DC8F0 /* IQUITestDebugSwitchCell.m */; }; 25 | BAC7A46C211D2C3A001DC8F0 /* IQUITestCodeMakerJSWDUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A441211D2C36001DC8F0 /* IQUITestCodeMakerJSWDUnit.m */; }; 26 | BAC7A46F211D2C3A001DC8F0 /* IQUITestCodeMakerCapabilities.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A445211D2C37001DC8F0 /* IQUITestCodeMakerCapabilities.m */; }; 27 | BAC7A473211D2C3A001DC8F0 /* IQUITestCodeMakerGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A44D211D2C38001DC8F0 /* IQUITestCodeMakerGenerator.m */; }; 28 | BAC7A474211D2C3A001DC8F0 /* IQUITestDebugTable.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A452211D2C38001DC8F0 /* IQUITestDebugTable.m */; }; 29 | BAC7A477211D2C3A001DC8F0 /* UIApplication+IQGlobalHook.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A457211D2C39001DC8F0 /* UIApplication+IQGlobalHook.m */; }; 30 | BAC7A479211D2C3A001DC8F0 /* IQUITestDebugServerInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BAC7A45D211D2C3A001DC8F0 /* IQUITestDebugServerInfoCell.m */; }; 31 | BAF70C4C2121A027000DB87A /* IQUITestCodeMakerJavaUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BAF70C4B2121A027000DB87A /* IQUITestCodeMakerJavaUnit.m */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXCopyFilesBuildPhase section */ 35 | BAC7A417211D2BA1001DC8F0 /* CopyFiles */ = { 36 | isa = PBXCopyFilesBuildPhase; 37 | buildActionMask = 2147483647; 38 | dstPath = "include/$(PRODUCT_NAME)"; 39 | dstSubfolderSpec = 16; 40 | files = ( 41 | BAC7A41F211D2BA1001DC8F0 /* IQUITestCodeMaker.h in CopyFiles */, 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXCopyFilesBuildPhase section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 7771CF60D4090766F8055EEB /* Pods-IQUITestCodeMaker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IQUITestCodeMaker.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IQUITestCodeMaker/Pods-IQUITestCodeMaker.debug.xcconfig"; sourceTree = ""; }; 49 | 7C584920E7804B3FCE51D218 /* Pods-IQUITestCodeMaker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IQUITestCodeMaker.release.xcconfig"; path = "Pods/Target Support Files/Pods-IQUITestCodeMaker/Pods-IQUITestCodeMaker.release.xcconfig"; sourceTree = ""; }; 50 | A9C52283157AB2941822867D /* libPods-IQUITestCodeMaker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-IQUITestCodeMaker.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | BAC7A419211D2BA1001DC8F0 /* libIQUITestCodeMaker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libIQUITestCodeMaker.a; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | BAC7A41C211D2BA1001DC8F0 /* IQUITestCodeMaker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMaker.h; sourceTree = ""; }; 53 | BAC7A425211D2C34001DC8F0 /* IQUITestOperationEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestOperationEvent.m; sourceTree = ""; }; 54 | BAC7A426211D2C34001DC8F0 /* IQUITestCodeMakerPythonUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerPythonUnit.m; sourceTree = ""; }; 55 | BAC7A427211D2C34001DC8F0 /* IQUITestDebugServerInfoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugServerInfoModel.m; sourceTree = ""; }; 56 | BAC7A428211D2C34001DC8F0 /* IQUITestDebugKeyValueCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugKeyValueCell.m; sourceTree = ""; }; 57 | BAC7A429211D2C34001DC8F0 /* IQUITestDebugScriptCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugScriptCell.m; sourceTree = ""; }; 58 | BAC7A42B211D2C34001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerJSPromiseUnit.m; sourceTree = ""; }; 59 | BAC7A42C211D2C34001DC8F0 /* IQUITestDebugSwitchCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugSwitchCell.h; sourceTree = ""; }; 60 | BAC7A42D211D2C34001DC8F0 /* IQUITestDebugScriptModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugScriptModel.m; sourceTree = ""; }; 61 | BAC7A42E211D2C34001DC8F0 /* IQUITestDebugSwitchModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugSwitchModel.m; sourceTree = ""; }; 62 | BAC7A430211D2C34001DC8F0 /* IQUITestCodeMakerGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerGenerator.h; sourceTree = ""; }; 63 | BAC7A431211D2C34001DC8F0 /* IQUITestOperationEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestOperationEvent.h; sourceTree = ""; }; 64 | BAC7A433211D2C35001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerJSPromiseUnit.h; sourceTree = ""; }; 65 | BAC7A434211D2C35001DC8F0 /* IQUITestDebugKeyValueModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugKeyValueModel.m; sourceTree = ""; }; 66 | BAC7A435211D2C35001DC8F0 /* IQUITestDebugServerInfoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugServerInfoCell.h; sourceTree = ""; }; 67 | BAC7A438211D2C35001DC8F0 /* IQUITestCodeMakerRubyUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerRubyUnit.m; sourceTree = ""; }; 68 | BAC7A439211D2C35001DC8F0 /* IQUITestCodeMakerFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerFactory.m; sourceTree = ""; }; 69 | BAC7A43A211D2C35001DC8F0 /* IQUITestDebugBall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugBall.m; sourceTree = ""; }; 70 | BAC7A43B211D2C35001DC8F0 /* IQUITestDebugScriptCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugScriptCell.h; sourceTree = ""; }; 71 | BAC7A43C211D2C35001DC8F0 /* IQUITestDebugSwitchCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugSwitchCell.m; sourceTree = ""; }; 72 | BAC7A43D211D2C35001DC8F0 /* UIApplication+IQGlobalHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIApplication+IQGlobalHook.h"; sourceTree = ""; }; 73 | BAC7A43E211D2C36001DC8F0 /* IQUITestDebugBall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugBall.h; sourceTree = ""; }; 74 | BAC7A43F211D2C36001DC8F0 /* IQUITestCodeMakerCapabilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerCapabilities.h; sourceTree = ""; }; 75 | BAC7A441211D2C36001DC8F0 /* IQUITestCodeMakerJSWDUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerJSWDUnit.m; sourceTree = ""; }; 76 | BAC7A444211D2C36001DC8F0 /* IQUITestDebugKeyValueModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugKeyValueModel.h; sourceTree = ""; }; 77 | BAC7A445211D2C37001DC8F0 /* IQUITestCodeMakerCapabilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerCapabilities.m; sourceTree = ""; }; 78 | BAC7A446211D2C37001DC8F0 /* IQUITestCodeMakerFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerFactory.h; sourceTree = ""; }; 79 | BAC7A44D211D2C38001DC8F0 /* IQUITestCodeMakerGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerGenerator.m; sourceTree = ""; }; 80 | BAC7A44E211D2C38001DC8F0 /* IQUITestDebugServerInfoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugServerInfoModel.h; sourceTree = ""; }; 81 | BAC7A44F211D2C38001DC8F0 /* IQUITestDebugSwitchModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugSwitchModel.h; sourceTree = ""; }; 82 | BAC7A450211D2C38001DC8F0 /* IQUITestDebugTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugTable.h; sourceTree = ""; }; 83 | BAC7A451211D2C38001DC8F0 /* IQUITestCodeMakerPythonUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerPythonUnit.h; sourceTree = ""; }; 84 | BAC7A452211D2C38001DC8F0 /* IQUITestDebugTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugTable.m; sourceTree = ""; }; 85 | BAC7A455211D2C39001DC8F0 /* IQUITestCodeMakerRubyUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerRubyUnit.h; sourceTree = ""; }; 86 | BAC7A456211D2C39001DC8F0 /* IQUITestProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestProtocol.h; sourceTree = ""; }; 87 | BAC7A457211D2C39001DC8F0 /* UIApplication+IQGlobalHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIApplication+IQGlobalHook.m"; sourceTree = ""; }; 88 | BAC7A458211D2C39001DC8F0 /* IQUITestCodeMakerJSWDUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerJSWDUnit.h; sourceTree = ""; }; 89 | BAC7A45A211D2C3A001DC8F0 /* IQUITestDebugKeyValueCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugKeyValueCell.h; sourceTree = ""; }; 90 | BAC7A45C211D2C3A001DC8F0 /* IQUITestDebugScriptModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IQUITestDebugScriptModel.h; sourceTree = ""; }; 91 | BAC7A45D211D2C3A001DC8F0 /* IQUITestDebugServerInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IQUITestDebugServerInfoCell.m; sourceTree = ""; }; 92 | BAF70C4A2121A027000DB87A /* IQUITestCodeMakerJavaUnit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IQUITestCodeMakerJavaUnit.h; sourceTree = ""; }; 93 | BAF70C4B2121A027000DB87A /* IQUITestCodeMakerJavaUnit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IQUITestCodeMakerJavaUnit.m; sourceTree = ""; }; 94 | /* End PBXFileReference section */ 95 | 96 | /* Begin PBXFrameworksBuildPhase section */ 97 | BAC7A416211D2BA1001DC8F0 /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | AAFFC991FED3AF210F8C4A7E /* libPods-IQUITestCodeMaker.a in Frameworks */, 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | /* End PBXFrameworksBuildPhase section */ 106 | 107 | /* Begin PBXGroup section */ 108 | 071D18DABC4D54DC61430BE9 /* Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | A9C52283157AB2941822867D /* libPods-IQUITestCodeMaker.a */, 112 | ); 113 | name = Frameworks; 114 | sourceTree = ""; 115 | }; 116 | B0F7F9C10FA222DB37BBCAEF /* Pods */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 7771CF60D4090766F8055EEB /* Pods-IQUITestCodeMaker.debug.xcconfig */, 120 | 7C584920E7804B3FCE51D218 /* Pods-IQUITestCodeMaker.release.xcconfig */, 121 | ); 122 | name = Pods; 123 | sourceTree = ""; 124 | }; 125 | BAC7A410211D2BA1001DC8F0 = { 126 | isa = PBXGroup; 127 | children = ( 128 | BAC7A41B211D2BA1001DC8F0 /* IQUITestCodeMaker */, 129 | BAC7A41A211D2BA1001DC8F0 /* Products */, 130 | B0F7F9C10FA222DB37BBCAEF /* Pods */, 131 | 071D18DABC4D54DC61430BE9 /* Frameworks */, 132 | ); 133 | sourceTree = ""; 134 | }; 135 | BAC7A41A211D2BA1001DC8F0 /* Products */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | BAC7A419211D2BA1001DC8F0 /* libIQUITestCodeMaker.a */, 139 | ); 140 | name = Products; 141 | sourceTree = ""; 142 | }; 143 | BAC7A41B211D2BA1001DC8F0 /* IQUITestCodeMaker */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | BAC7A41C211D2BA1001DC8F0 /* IQUITestCodeMaker.h */, 147 | BAC7A456211D2C39001DC8F0 /* IQUITestProtocol.h */, 148 | BAC7A431211D2C34001DC8F0 /* IQUITestOperationEvent.h */, 149 | BAC7A425211D2C34001DC8F0 /* IQUITestOperationEvent.m */, 150 | BAC7A43F211D2C36001DC8F0 /* IQUITestCodeMakerCapabilities.h */, 151 | BAC7A445211D2C37001DC8F0 /* IQUITestCodeMakerCapabilities.m */, 152 | BAC7A430211D2C34001DC8F0 /* IQUITestCodeMakerGenerator.h */, 153 | BAC7A44D211D2C38001DC8F0 /* IQUITestCodeMakerGenerator.m */, 154 | BAC7A446211D2C37001DC8F0 /* IQUITestCodeMakerFactory.h */, 155 | BAC7A439211D2C35001DC8F0 /* IQUITestCodeMakerFactory.m */, 156 | BAC7A455211D2C39001DC8F0 /* IQUITestCodeMakerRubyUnit.h */, 157 | BAC7A438211D2C35001DC8F0 /* IQUITestCodeMakerRubyUnit.m */, 158 | BAC7A451211D2C38001DC8F0 /* IQUITestCodeMakerPythonUnit.h */, 159 | BAC7A426211D2C34001DC8F0 /* IQUITestCodeMakerPythonUnit.m */, 160 | BAC7A458211D2C39001DC8F0 /* IQUITestCodeMakerJSWDUnit.h */, 161 | BAC7A441211D2C36001DC8F0 /* IQUITestCodeMakerJSWDUnit.m */, 162 | BAC7A433211D2C35001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.h */, 163 | BAC7A42B211D2C34001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.m */, 164 | BAF70C4A2121A027000DB87A /* IQUITestCodeMakerJavaUnit.h */, 165 | BAF70C4B2121A027000DB87A /* IQUITestCodeMakerJavaUnit.m */, 166 | BAC7A43E211D2C36001DC8F0 /* IQUITestDebugBall.h */, 167 | BAC7A43A211D2C35001DC8F0 /* IQUITestDebugBall.m */, 168 | BAC7A450211D2C38001DC8F0 /* IQUITestDebugTable.h */, 169 | BAC7A452211D2C38001DC8F0 /* IQUITestDebugTable.m */, 170 | BAC7A45A211D2C3A001DC8F0 /* IQUITestDebugKeyValueCell.h */, 171 | BAC7A428211D2C34001DC8F0 /* IQUITestDebugKeyValueCell.m */, 172 | BAC7A444211D2C36001DC8F0 /* IQUITestDebugKeyValueModel.h */, 173 | BAC7A434211D2C35001DC8F0 /* IQUITestDebugKeyValueModel.m */, 174 | BAC7A43B211D2C35001DC8F0 /* IQUITestDebugScriptCell.h */, 175 | BAC7A429211D2C34001DC8F0 /* IQUITestDebugScriptCell.m */, 176 | BAC7A45C211D2C3A001DC8F0 /* IQUITestDebugScriptModel.h */, 177 | BAC7A42D211D2C34001DC8F0 /* IQUITestDebugScriptModel.m */, 178 | BAC7A435211D2C35001DC8F0 /* IQUITestDebugServerInfoCell.h */, 179 | BAC7A45D211D2C3A001DC8F0 /* IQUITestDebugServerInfoCell.m */, 180 | BAC7A44E211D2C38001DC8F0 /* IQUITestDebugServerInfoModel.h */, 181 | BAC7A427211D2C34001DC8F0 /* IQUITestDebugServerInfoModel.m */, 182 | BAC7A42C211D2C34001DC8F0 /* IQUITestDebugSwitchCell.h */, 183 | BAC7A43C211D2C35001DC8F0 /* IQUITestDebugSwitchCell.m */, 184 | BAC7A44F211D2C38001DC8F0 /* IQUITestDebugSwitchModel.h */, 185 | BAC7A42E211D2C34001DC8F0 /* IQUITestDebugSwitchModel.m */, 186 | BAC7A43D211D2C35001DC8F0 /* UIApplication+IQGlobalHook.h */, 187 | BAC7A457211D2C39001DC8F0 /* UIApplication+IQGlobalHook.m */, 188 | ); 189 | path = IQUITestCodeMaker; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXGroup section */ 193 | 194 | /* Begin PBXNativeTarget section */ 195 | BAC7A418211D2BA1001DC8F0 /* IQUITestCodeMaker */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = BAC7A422211D2BA1001DC8F0 /* Build configuration list for PBXNativeTarget "IQUITestCodeMaker" */; 198 | buildPhases = ( 199 | 366D1F02B55276AD593C0271 /* [CP] Check Pods Manifest.lock */, 200 | BAC7A415211D2BA1001DC8F0 /* Sources */, 201 | BAC7A416211D2BA1001DC8F0 /* Frameworks */, 202 | BAC7A417211D2BA1001DC8F0 /* CopyFiles */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | ); 208 | name = IQUITestCodeMaker; 209 | productName = IQUITestCodeMaker; 210 | productReference = BAC7A419211D2BA1001DC8F0 /* libIQUITestCodeMaker.a */; 211 | productType = "com.apple.product-type.library.static"; 212 | }; 213 | /* End PBXNativeTarget section */ 214 | 215 | /* Begin PBXProject section */ 216 | BAC7A411211D2BA1001DC8F0 /* Project object */ = { 217 | isa = PBXProject; 218 | attributes = { 219 | LastUpgradeCheck = 0940; 220 | ORGANIZATIONNAME = lobster; 221 | TargetAttributes = { 222 | BAC7A418211D2BA1001DC8F0 = { 223 | CreatedOnToolsVersion = 9.4; 224 | }; 225 | }; 226 | }; 227 | buildConfigurationList = BAC7A414211D2BA1001DC8F0 /* Build configuration list for PBXProject "IQUITestCodeMaker" */; 228 | compatibilityVersion = "Xcode 9.3"; 229 | developmentRegion = en; 230 | hasScannedForEncodings = 0; 231 | knownRegions = ( 232 | en, 233 | ); 234 | mainGroup = BAC7A410211D2BA1001DC8F0; 235 | productRefGroup = BAC7A41A211D2BA1001DC8F0 /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | BAC7A418211D2BA1001DC8F0 /* IQUITestCodeMaker */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXShellScriptBuildPhase section */ 245 | 366D1F02B55276AD593C0271 /* [CP] Check Pods Manifest.lock */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 252 | "${PODS_ROOT}/Manifest.lock", 253 | ); 254 | name = "[CP] Check Pods Manifest.lock"; 255 | outputPaths = ( 256 | "$(DERIVED_FILE_DIR)/Pods-IQUITestCodeMaker-checkManifestLockResult.txt", 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | shellPath = /bin/sh; 260 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 261 | showEnvVarsInLog = 0; 262 | }; 263 | /* End PBXShellScriptBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | BAC7A415211D2BA1001DC8F0 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | BAC7A462211D2C3A001DC8F0 /* IQUITestDebugScriptCell.m in Sources */, 271 | BAC7A479211D2C3A001DC8F0 /* IQUITestDebugServerInfoCell.m in Sources */, 272 | BAC7A467211D2C3A001DC8F0 /* IQUITestCodeMakerRubyUnit.m in Sources */, 273 | BAC7A460211D2C3A001DC8F0 /* IQUITestDebugServerInfoModel.m in Sources */, 274 | BAC7A46C211D2C3A001DC8F0 /* IQUITestCodeMakerJSWDUnit.m in Sources */, 275 | BAC7A45E211D2C3A001DC8F0 /* IQUITestOperationEvent.m in Sources */, 276 | BAC7A469211D2C3A001DC8F0 /* IQUITestDebugBall.m in Sources */, 277 | BAC7A466211D2C3A001DC8F0 /* IQUITestDebugKeyValueModel.m in Sources */, 278 | BAC7A473211D2C3A001DC8F0 /* IQUITestCodeMakerGenerator.m in Sources */, 279 | BAC7A45F211D2C3A001DC8F0 /* IQUITestCodeMakerPythonUnit.m in Sources */, 280 | BAC7A463211D2C3A001DC8F0 /* IQUITestCodeMakerJSPromiseUnit.m in Sources */, 281 | BAC7A46A211D2C3A001DC8F0 /* IQUITestDebugSwitchCell.m in Sources */, 282 | BAC7A477211D2C3A001DC8F0 /* UIApplication+IQGlobalHook.m in Sources */, 283 | BAC7A464211D2C3A001DC8F0 /* IQUITestDebugScriptModel.m in Sources */, 284 | BAC7A474211D2C3A001DC8F0 /* IQUITestDebugTable.m in Sources */, 285 | BAC7A461211D2C3A001DC8F0 /* IQUITestDebugKeyValueCell.m in Sources */, 286 | BAC7A465211D2C3A001DC8F0 /* IQUITestDebugSwitchModel.m in Sources */, 287 | BAC7A468211D2C3A001DC8F0 /* IQUITestCodeMakerFactory.m in Sources */, 288 | BAC7A46F211D2C3A001DC8F0 /* IQUITestCodeMakerCapabilities.m in Sources */, 289 | BAF70C4C2121A027000DB87A /* IQUITestCodeMakerJavaUnit.m in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXSourcesBuildPhase section */ 294 | 295 | /* Begin XCBuildConfiguration section */ 296 | BAC7A420211D2BA1001DC8F0 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_ANALYZER_NONNULL = YES; 301 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_ENABLE_OBJC_WEAK = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 323 | CLANG_WARN_STRICT_PROTOTYPES = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 325 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | CODE_SIGN_IDENTITY = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = dwarf; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | ENABLE_TESTABILITY = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_DYNAMIC_NO_PIC = NO; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 348 | MTL_ENABLE_DEBUG_INFO = YES; 349 | ONLY_ACTIVE_ARCH = YES; 350 | SDKROOT = iphoneos; 351 | }; 352 | name = Debug; 353 | }; 354 | BAC7A421211D2BA1001DC8F0 /* Release */ = { 355 | isa = XCBuildConfiguration; 356 | buildSettings = { 357 | ALWAYS_SEARCH_USER_PATHS = NO; 358 | CLANG_ANALYZER_NONNULL = YES; 359 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_ENABLE_OBJC_WEAK = YES; 365 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 366 | CLANG_WARN_BOOL_CONVERSION = YES; 367 | CLANG_WARN_COMMA = YES; 368 | CLANG_WARN_CONSTANT_CONVERSION = YES; 369 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 370 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 371 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 384 | CLANG_WARN_UNREACHABLE_CODE = YES; 385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 386 | CODE_SIGN_IDENTITY = "iPhone Developer"; 387 | COPY_PHASE_STRIP = NO; 388 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 389 | ENABLE_NS_ASSERTIONS = NO; 390 | ENABLE_STRICT_OBJC_MSGSEND = YES; 391 | GCC_C_LANGUAGE_STANDARD = gnu11; 392 | GCC_NO_COMMON_BLOCKS = YES; 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 400 | MTL_ENABLE_DEBUG_INFO = NO; 401 | SDKROOT = iphoneos; 402 | VALIDATE_PRODUCT = YES; 403 | }; 404 | name = Release; 405 | }; 406 | BAC7A423211D2BA1001DC8F0 /* Debug */ = { 407 | isa = XCBuildConfiguration; 408 | baseConfigurationReference = 7771CF60D4090766F8055EEB /* Pods-IQUITestCodeMaker.debug.xcconfig */; 409 | buildSettings = { 410 | CODE_SIGN_STYLE = Automatic; 411 | OTHER_LDFLAGS = "-ObjC"; 412 | PRODUCT_NAME = "$(TARGET_NAME)"; 413 | SKIP_INSTALL = YES; 414 | TARGETED_DEVICE_FAMILY = "1,2"; 415 | }; 416 | name = Debug; 417 | }; 418 | BAC7A424211D2BA1001DC8F0 /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | baseConfigurationReference = 7C584920E7804B3FCE51D218 /* Pods-IQUITestCodeMaker.release.xcconfig */; 421 | buildSettings = { 422 | CODE_SIGN_STYLE = Automatic; 423 | OTHER_LDFLAGS = "-ObjC"; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | SKIP_INSTALL = YES; 426 | TARGETED_DEVICE_FAMILY = "1,2"; 427 | }; 428 | name = Release; 429 | }; 430 | /* End XCBuildConfiguration section */ 431 | 432 | /* Begin XCConfigurationList section */ 433 | BAC7A414211D2BA1001DC8F0 /* Build configuration list for PBXProject "IQUITestCodeMaker" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | BAC7A420211D2BA1001DC8F0 /* Debug */, 437 | BAC7A421211D2BA1001DC8F0 /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | BAC7A422211D2BA1001DC8F0 /* Build configuration list for PBXNativeTarget "IQUITestCodeMaker" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | BAC7A423211D2BA1001DC8F0 /* Debug */, 446 | BAC7A424211D2BA1001DC8F0 /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | /* End XCConfigurationList section */ 452 | }; 453 | rootObject = BAC7A411211D2BA1001DC8F0 /* Project object */; 454 | } 455 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcodeproj/xcshareddata/xcschemes/IQUITestCodeMaker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /IQUITestCodeMaker.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMaker.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/3. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | /*注意: 10 | 1.该组件通过运行时自动生成并获取标签,会有一定的性能开销,默认DEBUG模式开启,RELEASE模式关闭。 11 | 2.也可以通过打开#define IQ_UITEST_CODEMAKER_ENABLED 0 强制关闭该组件。 12 | */ 13 | 14 | //#define IQ_UITEST_CODEMAKER_ENABLED 0 15 | 16 | #ifdef IQ_UITEST_CODEMAKER_ENABLED 17 | #define IQ_CODEMAKER_ENABLED IQ_UITEST_CODEMAKER_ENABLED 18 | #else 19 | #define IQ_CODEMAKER_ENABLED DEBUG 20 | #endif 21 | 22 | /*Debug球注意事项: 23 | 1.默认对于Debug球相关UI不做hook操作。 24 | 2.可以通过打开//#define IQ_UITEST_DEBUGBALL_ENABLED 0 强制关闭Debug工具 25 | */ 26 | 27 | //#define IQ_UITEST_DEBUGBALL_ENABLED 0 28 | 29 | #ifdef IQ_UITEST_DEBUGBALL_ENABLED 30 | #define IQ_DEBUGBALL_ENABLED IQ_UITEST_DEBUGBALL_ENABLED 31 | #else 32 | #define IQ_DEBUGBALL_ENABLED DEBUG 33 | #endif 34 | 35 | /* 36 | 获取设备UDID注意事项:(用此工具,默认你身边没有Mac没有iTunes,没有iTools,没有Xcode等工具、算了撸主太懒还是先用蒲公英的吧) 37 | 1.此功能不能上架Appstore!!! 38 | 2.此功能不能上架Appstore!!! 39 | 3.此功能不能上架Appstore!!! 40 | 4.默认DEBUG开启,RELEASE关闭 41 | */ 42 | 43 | //#define IQ_UITEST_UDIDFETCH_ENABLED 0 44 | 45 | #ifdef IQ_UITEST_UDIDFETCH_ENABLED 46 | #define IQ_UDIDFETCH_ENABLED IQ_UITEST_UDIDFETCH_ENABLED 47 | #else 48 | #define IQ_UDIDFETCHL_ENABLED DEBUG 49 | #endif 50 | 51 | #define DebugView(prefixClsString) ([prefixClsString hasPrefix:@"IQUITestDebug"]) 52 | #define WhiteListView(prefixClsString) ([@[@"_UISnapshotWindow",@"UITableViewCellContentView",@"UISwitchModernVisualElement",@"UIFieldEditor",@"UITextField",@"UITextView"] containsObject:prefixClsString]) 53 | 54 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerCapabilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerCapabilities.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger,IQUITestDriverType ) { 12 | IQUITestDriverAppium = 0,/*default*/ 13 | IQUITestDriverMacaca = 1,/*support soon*/ 14 | }; 15 | 16 | @class IQAppiumCapabilities,IQMacacaCapabilities; 17 | 18 | @interface IQUITestCodeMakerCapabilities : NSObject 19 | 20 | @property (nonatomic, assign) IQUITestDriverType driverType; 21 | @property (nonatomic, strong) IQAppiumCapabilities *appiumCap; 22 | @property (nonatomic, strong) IQMacacaCapabilities *macacaCap; 23 | 24 | @end 25 | 26 | @interface IQAppiumCapabilities : NSObject 27 | 28 | @property (nonatomic, copy) NSString *appiumVersion;/*appium version 1.0*/ 29 | @property (nonatomic, copy) NSString *platformName;/*iOS*/ 30 | @property (nonatomic, copy) NSString *platformVersion;/*9.3~11.4*/ 31 | @property (nonatomic, copy) NSString *deviceName;/*iPhone 6s ..*/ 32 | #warning do not use it in relase mode !!! 33 | @property (nonatomic, copy) NSString *udid;/*device id*/ 34 | @property (nonatomic, copy) NSString *app;/*app path*/ 35 | @property (nonatomic, copy) NSString *serverAddress;/*127.0.0.1 default*/ 36 | @property (nonatomic, copy) NSString *serverPort;/*4723 default*/ 37 | @property (nonatomic, copy) NSString *appiumLanguage;/*Ruby default*/ 38 | @property (nonatomic, copy) NSString *autoAcceptAlerts;/*false default*/ 39 | @property (nonatomic, copy) NSString *interKeyDelay;/*click delay*/ 40 | @property (nonatomic, copy) NSString *showIOSLog;/*false default*/ 41 | @property (nonatomic, copy) NSString *waitTime;/*find element timeout,10s default*/ 42 | @property (nonatomic, copy) NSString *automationName;/*XCUITest default*/ 43 | 44 | @end 45 | 46 | @interface IQMacacaCapabilities : NSObject 47 | 48 | @property (nonatomic, copy) NSString *macacaLanguage;/*Ruby default*/ 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerCapabilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerCapabilities.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerCapabilities.h" 10 | #import 11 | #import 12 | 13 | static NSString *const kCapabilitiesKey = @"kCapabilitiesKey"; 14 | 15 | @implementation IQUITestCodeMakerCapabilities 16 | 17 | - (instancetype)init { 18 | if (self = [super init]) { 19 | _driverType = IQUITestDriverAppium; 20 | /* 21 | 1.本地有则取本地的cap。 22 | 2.本地没有则用默认的。 23 | */ 24 | 25 | NSDictionary *localCapDic = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 26 | if (localCapDic) { 27 | IQAppiumCapabilities *localCap = [IQAppiumCapabilities new]; 28 | for (NSString *key in localCapDic.allKeys) { 29 | [localCap setValue:localCapDic[key] forKey:key]; 30 | } 31 | self.appiumCap = localCap; 32 | } else { 33 | IQAppiumCapabilities *cap = [IQAppiumCapabilities new]; 34 | self.appiumCap = cap; 35 | } 36 | _macacaCap = nil; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)setAppiumCap:(IQAppiumCapabilities *)appiumCap { 42 | if (appiumCap) { 43 | _appiumCap = appiumCap; 44 | } 45 | /*更新本地缓存 46 | 1.将model转换成NSDictonary存入本地。 47 | */ 48 | NSDictionary *cacheCap = [self convertModelToDict:_appiumCap]; 49 | [[NSUserDefaults standardUserDefaults] setObject:cacheCap forKey:kCapabilitiesKey]; 50 | [[NSUserDefaults standardUserDefaults] synchronize]; 51 | } 52 | 53 | + (NSDictionary *)appiumCapFromCache { 54 | NSDictionary *localCapDic = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 55 | return localCapDic; 56 | } 57 | 58 | /*只能处理简单属性*/ 59 | - (NSDictionary *)convertModelToDict:(NSObject *)object { 60 | NSMutableDictionary *dic = [NSMutableDictionary dictionary]; 61 | unsigned int count; 62 | objc_property_t *propertyList = class_copyPropertyList([object class], &count); 63 | 64 | for (int i = 0; i < count; i++) { 65 | objc_property_t property = propertyList[i]; 66 | const char *cName = property_getName(property); 67 | NSString *name = [NSString stringWithUTF8String:cName]; 68 | [dic setValue:[object valueForKey:name] forKey:name]; 69 | } 70 | 71 | return [dic copy]; 72 | } 73 | 74 | @end 75 | 76 | @implementation IQAppiumCapabilities : NSObject 77 | 78 | - (instancetype)init { 79 | if (self = [super init]) { 80 | _appiumVersion = @"1.8.1"; 81 | _platformName = @"iOS"; 82 | _platformVersion = [UIDevice currentDevice].systemVersion; 83 | _deviceName = @"iPhone X"; 84 | _udid = @""; 85 | NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; 86 | _app = [NSString stringWithFormat:@"/Users/lobster/Desktop/%@.app",bundleName]; 87 | _serverAddress = @"127.0.0.1"; 88 | _serverPort = @"4723"; 89 | _appiumLanguage = @"Ruby"; 90 | _autoAcceptAlerts = @"1"; 91 | _showIOSLog = @"0"; 92 | _interKeyDelay = @"0"; 93 | _waitTime = @"10"; 94 | _automationName = @"XCUITest"; 95 | } 96 | return self; 97 | } 98 | 99 | @end 100 | 101 | @implementation IQMacacaCapabilities : NSObject 102 | 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerFactory.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IQUITestCodeMakerCapabilities,IQUITestOperationEvent; 12 | 13 | @interface IQUITestCodeMakerFactory : NSObject 14 | 15 | @property (nonatomic, strong, readonly) IQUITestCodeMakerCapabilities *cap; 16 | @property (nonatomic, copy, readonly) NSString *scriptPath; 17 | @property (nonatomic, assign) NSInteger eventIndex; 18 | @property (nonatomic, strong) NSMutableArray *eventQueue; 19 | @property (nonatomic, assign) BOOL isConverting;/*是否正在进行代码转换*/ 20 | @property (nonatomic, assign) NSInteger templateCodeFlag; 21 | @property (nonatomic, assign) NSInteger endCodeFlag; 22 | 23 | + (IQUITestCodeMakerFactory *)handleTaskUnit; 24 | + (IQUITestCodeMakerFactory *)handleTaskUnitWithCap:(IQUITestCodeMakerCapabilities *)cap; 25 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op; 26 | - (void)storeProductCode:(NSString *)code; 27 | - (void)convertEvetQueueToScript; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerFactory.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @interface IQUITestCodeMakerFactory () 14 | 15 | @property (nonatomic, strong, readwrite) IQUITestCodeMakerCapabilities *cap; 16 | @property (nonatomic, copy, readwrite) NSString *scriptPath; 17 | @property (nonatomic, strong) dispatch_semaphore_t semaphore; 18 | 19 | @end 20 | 21 | @implementation IQUITestCodeMakerFactory 22 | 23 | + (NSDictionary *)unitStructureMap { 24 | NSDictionary *unitStruct = @{ 25 | @"Ruby" :@"IQUITestCodeMakerRubyUnit", 26 | @"Python" :@"IQUITestCodeMakerPythonUnit", 27 | @"JSWdio" :@"IQUITestCodeMakerJSWDUnit", 28 | @"JSPromise" :@"IQUITestCodeMakerJSPromiseUnit", 29 | @"Java" :@"IQUITestCodeMakerJavaUnit", 30 | @"OC" :@"IQUITestCodeMakerOCUnit", 31 | @"Swift" :@"IQUITestCodeMakerSwiftUnit" 32 | }; 33 | return unitStruct; 34 | } 35 | 36 | + (IQUITestCodeMakerFactory *)handleTaskUnit { 37 | return [self handleTaskUnitWithCap:[IQUITestCodeMakerCapabilities new]];/*default cap*/ 38 | } 39 | 40 | + (IQUITestCodeMakerFactory *)handleTaskUnitWithCap:(IQUITestCodeMakerCapabilities *)cap { 41 | NSDictionary *unitMap = [self unitStructureMap]; 42 | NSString *unitString = @""; 43 | if (cap.driverType == 0) { 44 | unitString = unitMap[cap.appiumCap.appiumLanguage]; 45 | } else { 46 | unitString = unitMap[cap.macacaCap.macacaLanguage]; 47 | } 48 | IQUITestCodeMakerFactory *unit = [[NSClassFromString(unitString) alloc]init]; 49 | unit.cap = cap; 50 | unit.eventQueue = [NSMutableArray array]; 51 | return unit; 52 | } 53 | 54 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 55 | /*override this method*/ 56 | 57 | } 58 | 59 | - (void)convertEvetQueueToScript { 60 | 61 | } 62 | 63 | - (void)storeProductCode:(NSString *)code { 64 | if (!code) { 65 | return; 66 | } 67 | 68 | dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); 69 | NSError *error; 70 | if (![[NSFileManager defaultManager] fileExistsAtPath:self.scriptPath]) { 71 | [code writeToFile:self.scriptPath atomically:NO encoding:NSUTF8StringEncoding error:&error]; 72 | }else{ 73 | NSString *originCode = [NSString stringWithContentsOfFile:self.scriptPath encoding:NSUTF8StringEncoding error:nil]; 74 | originCode = [originCode stringByAppendingString:code]; 75 | [originCode writeToFile:self.scriptPath atomically:NO encoding:NSUTF8StringEncoding error:&error]; 76 | } 77 | dispatch_semaphore_signal(self.semaphore); 78 | } 79 | 80 | #pragma mark--Getters & Setters-- 81 | - (NSString *)scriptPath{ 82 | if (!_scriptPath) { 83 | NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); 84 | NSString *documentDirectory = [directoryPaths objectAtIndex:0]; 85 | NSString *scriptDir = [documentDirectory stringByAppendingString:@"/IQScripts/"]; 86 | if (![[NSFileManager defaultManager] fileExistsAtPath:scriptDir]) { 87 | [[NSFileManager defaultManager] createDirectoryAtPath:scriptDir withIntermediateDirectories:YES attributes:nil error:nil]; 88 | } 89 | NSString *suffix = [self suffix]; 90 | _scriptPath = [scriptDir stringByAppendingPathComponent:[NSString stringWithFormat:@"scriptCode.%@",suffix]]; 91 | NSLog(@"scriptCode.rb:%@",_scriptPath); 92 | } 93 | return _scriptPath; 94 | } 95 | 96 | - (dispatch_semaphore_t)semaphore { 97 | if (!_semaphore){ 98 | _semaphore = dispatch_semaphore_create(1); 99 | } 100 | return _semaphore; 101 | } 102 | 103 | - (NSString *)suffix { 104 | NSDictionary *suffixMap = @{ 105 | @"Ruby" :@"rb", 106 | @"Python" :@"py", 107 | @"JSWdio" :@"js", 108 | @"JSPromise":@"js", 109 | @"Java" :@"java", 110 | }; 111 | NSString *suf = nil; 112 | if (self.cap.driverType == 0) { 113 | suf = suffixMap[self.cap.appiumCap.appiumLanguage]; 114 | } else { 115 | suf = suffixMap[self.cap.macacaCap.macacaLanguage]; 116 | } 117 | return suf; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerGenerator.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IQUITestProtocol.h" 11 | 12 | @class IQUITestCodeMakerFactory,GCDWebServer; 13 | 14 | void IQRuntimeMethodExchange(Class aClass, SEL oldSEL, SEL newSEL); 15 | 16 | @interface IQUITestCodeMakerGenerator : NSObject 17 | 18 | @property (nonatomic, strong, readonly) IQUITestCodeMakerFactory *factory; 19 | @property (nonatomic, strong, readonly) GCDWebServer *webServer; 20 | 21 | + (instancetype)sharePersistent; 22 | - (void)hook; 23 | - (void)handleConvertTaskWithIdentifier:(NSString *)identifier; 24 | - (void)handleCapChangeTaskWithKey:(NSString *)key value:(NSString *)value; 25 | - (void)handleRecordControlEventWithState:(BOOL)state; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerGenerator.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerGenerator.h" 10 | #import "IQUITestCodeMaker.h" 11 | #import "IQUITestOperationEvent.h" 12 | #import "IQUITestCodeMakerFactory.h" 13 | #import 14 | #import 15 | #import 16 | #import "IQUITestDebugBall.h" 17 | #import "IQUITestCodeMakerCapabilities.h" 18 | #import "GCDWebServer.h" 19 | #import 20 | 21 | static IQUITestCodeMakerGenerator *persistent = nil; 22 | static NSString *const kAutoSetIdentifier = @"[A]"; 23 | static NSString *const kManualIdentifier = @"[M]"; 24 | 25 | static NSString *const kViewHierarchy = @"VHierarchy"; 26 | static NSString *const kViewTag = @"VTag"; 27 | static NSString *const kViewIvarName = @"VIvar"; 28 | static NSString *const kViewText = @"VText"; 29 | static NSString *const kViewTitle = @"VTitle"; 30 | static NSString *const kViewImageName = @"VImage"; 31 | static NSString *const kViewSection = @"VSection"; 32 | static NSString *const kViewIndexRow = @"VRow"; 33 | 34 | static NSString *const kCapabilitiesKey = @"kCapabilitiesKey"; 35 | 36 | void IQTapTask(id target) { 37 | IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 38 | op.eventType = IQUIEventTap; 39 | op.identifier= [target accessibilityIdentifier]; 40 | 41 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 42 | [persistent.factory produceCodeWithOperationEvent:op]; 43 | } 44 | 45 | void IQTapTaskWithLocation(CGPoint point) { 46 | IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 47 | op.locateStrategy = IQElementLocateByCoordinate; 48 | op.eventType = IQUIEventTap; 49 | op.touchPoint = point; 50 | 51 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 52 | [persistent.factory produceCodeWithOperationEvent:op]; 53 | } 54 | 55 | void IQSendKeyTask(id target) { 56 | IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 57 | op.eventType = IQUIEventSendKey; 58 | op.identifier = [target accessibilityIdentifier]; 59 | 60 | if ([target isKindOfClass:[UITextField class]]) { 61 | op.value = [(UITextField *)target text]; 62 | } else if ([target isKindOfClass:[UITextView class]]) { 63 | op.value = [(UITextView *)target text]; 64 | } 65 | 66 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 67 | [persistent.factory produceCodeWithOperationEvent:op]; 68 | } 69 | 70 | void IQQuitTask(void) { 71 | 72 | } 73 | 74 | void IQRuntimeMethodExchange(Class aClass, SEL oldSEL, SEL newSEL) { 75 | Method originalMethod = class_getInstanceMethod(aClass, oldSEL); 76 | Method swizzledMethod = class_getInstanceMethod(aClass, newSEL); 77 | BOOL didAddMethod = 78 | class_addMethod(aClass, 79 | oldSEL, 80 | method_getImplementation(swizzledMethod), 81 | method_getTypeEncoding(swizzledMethod)); 82 | if (didAddMethod) { 83 | class_replaceMethod(aClass, 84 | newSEL, 85 | method_getImplementation(originalMethod), 86 | method_getTypeEncoding(originalMethod)); 87 | } else { 88 | method_exchangeImplementations(originalMethod, swizzledMethod); 89 | } 90 | } 91 | 92 | /*hook touchBegin\touchMoved\touchCanceled*/ 93 | static NSMutableDictionary *> *SwizzledClassMethods() 94 | { 95 | static dispatch_once_t onceToken; 96 | static NSMutableDictionary *swizzledMethods = nil; 97 | dispatch_once(&onceToken, ^{ 98 | swizzledMethods = [[NSMutableDictionary alloc] init]; 99 | }); 100 | 101 | return swizzledMethods; 102 | } 103 | static void ImplementTouchMethodsIfNeeded(Class viewClass, SEL aSelector) 104 | { 105 | NSCParameterAssert(viewClass && aSelector); 106 | if (!viewClass || !aSelector) { 107 | return; 108 | } 109 | Class superclass = class_getSuperclass(viewClass); 110 | NSCParameterAssert(superclass); 111 | if (!superclass || !class_getInstanceMethod(superclass, aSelector)) { 112 | return; 113 | } 114 | 115 | NSString *className = NSStringFromClass(viewClass); 116 | NSString *methodName = NSStringFromSelector(aSelector); 117 | NSMutableSet *swizzledMethods = [SwizzledClassMethods() objectForKey:className]; 118 | if ([swizzledMethods containsObject:methodName]) { 119 | return; 120 | } 121 | 122 | IMP defaultIMP = imp_implementationWithBlock(^(id self, NSSet *touches, UIEvent *event) { 123 | struct objc_super super = { 124 | .receiver = self, 125 | .super_class = superclass 126 | }; 127 | void (*touchesEventHandler)(struct objc_super *, SEL, NSSet *, UIEvent *) = (__typeof__(touchesEventHandler))objc_msgSendSuper; 128 | return touchesEventHandler(&super, aSelector, touches, event); 129 | }); 130 | Method method = class_getInstanceMethod(superclass, aSelector); 131 | class_addMethod(viewClass, aSelector, defaultIMP, method_getTypeEncoding(method)); 132 | 133 | if(swizzledMethods == nil) { 134 | swizzledMethods = [[NSMutableSet alloc] init]; 135 | [SwizzledClassMethods() setObject:swizzledMethods forKey:className]; 136 | } 137 | [swizzledMethods addObject:methodName]; 138 | } 139 | 140 | #pragma mark--UITextField-- 141 | @implementation UITextField (IQRunTimeHook) 142 | 143 | + (void)IQHook { 144 | IQRuntimeMethodExchange([UITextField class], @selector(setDelegate:), @selector(IQ_setDelegate:)); 145 | } 146 | 147 | - (void)IQ_setDelegate:(id)delegate { 148 | [self IQ_setDelegate:delegate]; 149 | 150 | if (DebugView(NSStringFromClass([self class]))) { 151 | return; 152 | } 153 | 154 | /*hook @selector(tableView:cellForRowAtIndexPath:)*/ 155 | if (![delegate respondsToSelector:@selector(textFieldDidEndEditing:)]) { 156 | return; 157 | } 158 | 159 | Method originMethod = class_getInstanceMethod([delegate class], @selector(textFieldDidEndEditing:)); 160 | IMP originImp = method_getImplementation(originMethod); 161 | Method currentMethod = class_getInstanceMethod([self class], @selector(IQ_textFieldDidEndEditing:)); 162 | IMP currentImp = method_getImplementation(currentMethod); 163 | 164 | class_addMethod([delegate class], @selector(IQ_textFieldDidEndEditing:), currentImp, method_getTypeEncoding(currentMethod)); 165 | 166 | BOOL didAddMethod = class_addMethod([delegate class], @selector(textFieldDidEndEditing:), currentImp, method_getTypeEncoding(currentMethod)); 167 | 168 | if (didAddMethod) { 169 | class_replaceMethod([delegate class], @selector(IQ_textFieldDidEndEditing:), originImp, method_getTypeEncoding(originMethod)); 170 | } else { 171 | IQRuntimeMethodExchange([delegate class], @selector(textFieldDidEndEditing:), @selector(IQ_textFieldDidEndEditing:)); 172 | } 173 | 174 | } 175 | 176 | - (void)IQ_textFieldDidEndEditing:(UITextField *)textField { 177 | [self IQ_textFieldDidEndEditing:textField]; 178 | IQSendKeyTask(textField); 179 | } 180 | 181 | @end 182 | 183 | #pragma mark--UINavigationController-- 184 | 185 | @implementation UINavigationController (IQRunTimeHook) 186 | 187 | + (void)IQHook { 188 | IQRuntimeMethodExchange([UINavigationController class], @selector(navigationBar:shouldPopItem:), @selector(IQ_navigationBar:shouldPopItem:)); 189 | } 190 | 191 | - (BOOL)IQ_navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { 192 | BOOL ret = [self IQ_navigationBar:navigationBar shouldPopItem:item]; 193 | 194 | #warning 系统自带导航需要在此截获事件 195 | UINavigationItem *backItem = navigationBar.backItem; 196 | 197 | IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 198 | op.eventType = IQUIEventTap; 199 | op.identifier= backItem.title; 200 | 201 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 202 | [persistent.factory produceCodeWithOperationEvent:op]; 203 | 204 | return ret; 205 | } 206 | 207 | @end 208 | 209 | #pragma mark--UIGestureRecognizer-- 210 | 211 | @implementation UIGestureRecognizer (IQRunTimeHook) 212 | 213 | + (void)IQHook { 214 | IQRuntimeMethodExchange([UIGestureRecognizer class], @selector(initWithTarget:action:), @selector(IQ_initWithTarget:action:)); 215 | IQRuntimeMethodExchange([UIGestureRecognizer class], @selector(addTarget:action:), @selector(IQ_addTarget:action:)); 216 | } 217 | 218 | - (instancetype)IQ_initWithTarget:(nullable id)target action:(nullable SEL)action { 219 | id ges = [self IQ_initWithTarget:target action:action]; 220 | 221 | if (DebugView(NSStringFromClass([target class]))) { 222 | return ges; 223 | } 224 | 225 | if (![target respondsToSelector:action]) { 226 | return ges; 227 | } 228 | 229 | if (![self isKindOfClass:[UITapGestureRecognizer class]]) { 230 | return ges; 231 | } 232 | 233 | Method originMethod = class_getInstanceMethod([target class], action); 234 | IMP originImp = method_getImplementation(originMethod); 235 | Method currentMethod = class_getInstanceMethod([self class], @selector(IQ_delegateMethodWithFirstArg:)); 236 | IMP currentImp = method_getImplementation(currentMethod); 237 | 238 | class_addMethod([target class], @selector(IQ_delegateMethodWithFirstArg:), currentImp, method_getTypeEncoding(currentMethod)); 239 | 240 | BOOL didAddMethod = class_addMethod([target class], action, currentImp, method_getTypeEncoding(currentMethod)); 241 | 242 | if (didAddMethod) { 243 | class_replaceMethod([target class], @selector(IQ_delegateMethodWithFirstArg:), originImp, method_getTypeEncoding(originMethod)); 244 | } else { 245 | IQRuntimeMethodExchange([target class], action, @selector(IQ_delegateMethodWithFirstArg:)); 246 | } 247 | 248 | return ges; 249 | } 250 | 251 | - (void)IQ_addTarget:(id)target action:(SEL)action { 252 | [self IQ_addTarget:target action:action]; 253 | 254 | if (DebugView(NSStringFromClass([target class]))) { 255 | return; 256 | } 257 | 258 | if (![target respondsToSelector:action]) { 259 | return; 260 | } 261 | /*一个视图添加多个手势,hook会出现问题*/ 262 | if (![self isKindOfClass:[UITapGestureRecognizer class]]) { 263 | return; 264 | } 265 | 266 | Method originMethod = class_getInstanceMethod([target class], action); 267 | IMP originImp = method_getImplementation(originMethod); 268 | Method currentMethod = class_getInstanceMethod([self class], @selector(IQ_delegateMethodWithFirstArg:)); 269 | IMP currentImp = method_getImplementation(currentMethod); 270 | 271 | class_addMethod([target class], @selector(IQ_delegateMethodWithFirstArg:), currentImp, method_getTypeEncoding(currentMethod)); 272 | 273 | BOOL didAddMethod = class_addMethod([target class], action, currentImp, method_getTypeEncoding(currentMethod)); 274 | 275 | if (didAddMethod) { 276 | class_replaceMethod([target class], @selector(IQ_delegateMethodWithFirstArg:), originImp, method_getTypeEncoding(originMethod)); 277 | } else { 278 | IQRuntimeMethodExchange([target class], action, @selector(IQ_delegateMethodWithFirstArg:)); 279 | } 280 | 281 | } 282 | 283 | - (void)IQ_delegateMethodWithFirstArg:(id)arg,... NS_REQUIRES_NIL_TERMINATION { 284 | NSMutableArray *array = [NSMutableArray array]; 285 | if (arg){ 286 | va_list args; 287 | id cusorObj; 288 | va_start(args, arg); 289 | while((cusorObj = va_arg(args, id))) { 290 | [array addObject:cusorObj]; 291 | } 292 | va_end(args); 293 | } 294 | 295 | switch (array.count) { 296 | case 0: 297 | { 298 | [self IQ_delegateMethodWithFirstArg:nil]; 299 | } 300 | break; 301 | case 1: 302 | { 303 | [self IQ_delegateMethodWithFirstArg:array[0],nil]; 304 | } 305 | break; 306 | case 2: 307 | { 308 | [self IQ_delegateMethodWithFirstArg:array[0],array[1],nil]; 309 | } 310 | break; 311 | case 3: 312 | { 313 | [self IQ_delegateMethodWithFirstArg:array[0],array[1],array[2],nil]; 314 | } 315 | break; 316 | case 4: 317 | { 318 | [self IQ_delegateMethodWithFirstArg:array[0],array[1],array[2],array[3],nil]; 319 | } 320 | break; 321 | case 5: 322 | { 323 | [self IQ_delegateMethodWithFirstArg:array[0],array[1],array[2],array[3],array[4],nil]; 324 | } 325 | break; 326 | 327 | default: 328 | break; 329 | } 330 | 331 | id target = self; 332 | 333 | IQTapTask(target); 334 | 335 | } 336 | 337 | 338 | @end 339 | 340 | #pragma mark--UIImage-- 341 | @implementation UIImage (IQRunTimeHook) 342 | 343 | + (void)IQHook { 344 | IQRuntimeMethodExchange([UIImage class], @selector(imageNamed:), @selector(IQ_imageNamed:)); 345 | IQRuntimeMethodExchange([UIImage class], @selector(imageWithContentsOfFile:), @selector(IQ_imageWithContentsOfFile:)); 346 | IQRuntimeMethodExchange([UIImage class], @selector(accessibilityIdentifier), @selector(IQ_accessibilityIdentifier)); 347 | } 348 | 349 | + (UIImage *)IQ_imageNamed:(NSString *)imageName{ 350 | UIImage *image = [UIImage IQ_imageNamed:imageName]; 351 | image.accessibilityIdentifier = imageName; 352 | return image; 353 | } 354 | 355 | + (UIImage *)IQ_imageWithContentsOfFile:(NSString *)path 356 | { 357 | UIImage *image = [UIImage IQ_imageWithContentsOfFile:path]; 358 | NSArray *components = [path pathComponents]; 359 | if (components.count > 0) { 360 | image.accessibilityIdentifier = components.lastObject; 361 | } 362 | else { 363 | image.accessibilityIdentifier = path; 364 | } 365 | return image; 366 | } 367 | 368 | - (id)assetName {return nil;} 369 | 370 | - (NSString *)IQ_accessibilityIdentifier { 371 | NSString *IQ_accessibilityIdentifier = [self IQ_accessibilityIdentifier]; 372 | if (IQ_accessibilityIdentifier.length == 0 && [self respondsToSelector:@selector(imageAsset)]) { 373 | IQ_accessibilityIdentifier = [(id)self.imageAsset assetName]; 374 | self.accessibilityIdentifier = IQ_accessibilityIdentifier; 375 | } 376 | 377 | return IQ_accessibilityIdentifier; 378 | } 379 | 380 | @end 381 | 382 | #pragma mark--UIResponder-- 383 | @implementation UIResponder (IQRunTimeHook) 384 | 385 | -(NSString *)nameWithInstance:(id)instance { 386 | unsigned int numIvars = 0; 387 | NSString *key=nil; 388 | Ivar * ivars = class_copyIvarList([self class], &numIvars); 389 | for(int i = 0; i < numIvars; i++) { 390 | Ivar thisIvar = ivars[i]; 391 | const char *type = ivar_getTypeEncoding(thisIvar); 392 | NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding]; 393 | if (![stringType hasPrefix:@"@"]) { 394 | continue; 395 | } 396 | if ((object_getIvar(self, thisIvar) == instance)) { 397 | key = [NSString stringWithUTF8String:ivar_getName(thisIvar)]; 398 | break; 399 | } 400 | } 401 | free(ivars); 402 | return key; 403 | } 404 | 405 | - (NSString *)findNameWithInstance:(UIView *) instance 406 | { 407 | id nextResponder = [self nextResponder]; 408 | NSString *name = [self nameWithInstance:instance]; 409 | if (!name) { 410 | return [nextResponder findNameWithInstance:instance]; 411 | } 412 | if ([name hasPrefix:@"_"]) { 413 | name = [name substringFromIndex:1]; 414 | } 415 | return name; 416 | } 417 | 418 | @end 419 | 420 | #pragma mark--UIView-- 421 | @implementation UIView (IQRunTimeHook) 422 | 423 | + (void)IQHook { 424 | IQRuntimeMethodExchange([UIView class], @selector(accessibilityIdentifier), @selector(IQ_accessibilityIdentifier)); 425 | IQRuntimeMethodExchange([UIView class], @selector(addGestureRecognizer:), @selector(IQ_addGestureRecognizer:)); 426 | #warning has problem !!! fix me !!! 427 | ImplementTouchMethodsIfNeeded([UIView class], @selector(touchesBegan:withEvent:)); 428 | } 429 | 430 | - (NSString *)IQ_accessibilityIdentifier { 431 | NSLog(@"========>%@",NSStringFromClass([self class])); 432 | 433 | if (DebugView(NSStringFromClass([self class]))) { 434 | return [self IQ_accessibilityIdentifier]; 435 | } 436 | 437 | /*apple一些系统控件会自动设置accessibilityIdentifier,为视力不好的人自动设置标识。例如系统导航的返回按钮等*/ 438 | NSString *accessibilityIdentifier = [self IQ_accessibilityIdentifier]; 439 | 440 | /*针对于UICollectionViewCell、UITableViewCell一般需要根据indexPath.section和indexPth.row以及tableView所属的UIViewController唯一确定其identifier(需要考虑一个controller是多个表视图的delegate的情况)*/ 441 | /*若identifier已经有值且开发手动代码设置的,直接return其identifier*/ 442 | /*若identifier已经有值且prefix是自动代码设置的,直接return其identifier*/ 443 | /*若以上不满足,则获取控件的基本属性,尽可能的达到唯一标识的目的。 444 | 基本属性(基本不会变的):1.tag。2.在一类中的property name或者成员变量名称。 445 | UILabel:1.text。。 446 | UIButton:1.title。2.imageName。 447 | UIImageView:1.imageName。 448 | 其他类型:获取其在父视图的同类型的索引(Debug模式对于可交互的控件,可考虑回溯到controller级,懒加载基本能保证获取控件id时的视图基本是全的,这样还有个问题就是,视图层级可能因为接口或者某些原因导致变化)。 449 | */ 450 | 451 | if ([self isKindOfClass:[UITableViewCell class]] || [self isKindOfClass:[UICollectionViewCell class]]) { 452 | return accessibilityIdentifier; 453 | } 454 | if ([accessibilityIdentifier hasPrefix:kManualIdentifier]) { 455 | /*已手动设置*/ 456 | return accessibilityIdentifier; 457 | } 458 | if ([accessibilityIdentifier hasPrefix:kAutoSetIdentifier]) { 459 | /*已自动设置*/ 460 | return accessibilityIdentifier; 461 | } 462 | 463 | NSMutableDictionary *jsonFormat = [NSMutableDictionary dictionary]; 464 | [jsonFormat setValue:@"" forKey:kViewHierarchy]; 465 | [jsonFormat setValue:@"" forKey:kViewTag]; 466 | [jsonFormat setValue:@"" forKey:kViewIvarName]; 467 | NSString *identifierJsonFormat = @""; 468 | NSInteger tag = self.tag; 469 | /*从父类中查询ivar name*/ 470 | NSString *ivarName = [self.superview findNameWithInstance:self]; 471 | 472 | if ([self isKindOfClass:[UILabel class]]) { 473 | NSString *text = [(UILabel *)self text]?:@""; 474 | NSInteger index = 0; 475 | if ([[self gestureRecognizers] count]) { 476 | index = [self indexOfCurrentViewInSuperView:self]; 477 | } 478 | NSString *hierarchy = [NSString stringWithFormat:@"%@[%ld]",NSStringFromClass([self class]),index]; 479 | identifierJsonFormat = [identifierJsonFormat stringByAppendingString:[NSString stringWithFormat:@"%@{%@:%@,%@:%ld,%@:%@,%@:%@}",kAutoSetIdentifier,kViewHierarchy,hierarchy,kViewTag,tag,kViewIvarName,ivarName,kViewText,text]]; 480 | } else if ([self isKindOfClass:[UIButton class]]) { 481 | UIButton *btn = (UIButton *)self; 482 | NSString *btnTitle = btn.titleLabel.text?:@""; 483 | NSInteger index = [self indexOfCurrentViewInSuperView:self]; 484 | NSString *imageName = btn.imageView.image.accessibilityIdentifier; 485 | #warning 打开按钮的图片会影响appium定位 fix me !!! 486 | NSString *hierarchy = [NSString stringWithFormat:@"%@[%ld]",NSStringFromClass([self class]),index]; 487 | identifierJsonFormat = [identifierJsonFormat stringByAppendingString:[NSString stringWithFormat:@"%@{%@:%@,%@:%ld,%@:%@,%@:%@}",kAutoSetIdentifier,kViewHierarchy,hierarchy,kViewTag,tag,kViewIvarName,ivarName,kViewTitle,btnTitle]]; 488 | } else if ([self isKindOfClass:[UIImageView class]]) { 489 | UIImageView *imageView = (UIImageView *)self; 490 | NSString *imageName = imageView.image.accessibilityIdentifier; 491 | NSInteger index = 0; 492 | // if ([[self gestureRecognizers] count]) { 493 | index = [self indexOfCurrentViewInSuperView:self]; 494 | // } 495 | NSString *hierarchy = [NSString stringWithFormat:@"%@[%ld]",NSStringFromClass([self class]),index]; 496 | identifierJsonFormat = [identifierJsonFormat stringByAppendingString:[NSString stringWithFormat:@"%@{%@:%@,%@:%ld,%@:%@,%@:%@}",kAutoSetIdentifier,kViewHierarchy,hierarchy,kViewTag,tag,kViewIvarName,ivarName,kViewImageName,imageName]]; 497 | } else { 498 | #warning 通过touch来响应操作事件会过滤掉,有待解决。 499 | NSInteger index = 0; 500 | if ([NSStringFromClass([self class]) isEqualToString:@"BaseBottomBarItem"]) { 501 | 502 | } 503 | if ([[self gestureRecognizers] count]) { /*打开则会把通过touch事件操作给过滤掉*/ 504 | index = [self indexOfCurrentViewInSuperView:self]; 505 | } 506 | 507 | NSString *hierarchy = [NSString stringWithFormat:@"%@[%ld]",NSStringFromClass([self class]),index]; 508 | identifierJsonFormat = [identifierJsonFormat stringByAppendingString:[NSString stringWithFormat:@"%@{%@:%@,%@:%ld,%@:%@}",kAutoSetIdentifier,kViewHierarchy,hierarchy,kViewTag,tag,kViewIvarName,ivarName]]; 509 | } 510 | if (!identifierJsonFormat.length) { 511 | identifierJsonFormat = @"Oh,My God!"; 512 | } 513 | [self setAccessibilityIdentifier:identifierJsonFormat]; 514 | return identifierJsonFormat; 515 | } 516 | 517 | - (NSInteger)indexOfCurrentViewInSuperView:(UIView *)currentView { 518 | 519 | NSInteger cursor= 0; 520 | NSString *cls = NSStringFromClass([currentView class]); 521 | for (UIView *subview in [self.superview subviews]) { 522 | NSString *subCls = NSStringFromClass([subview class]); 523 | if ([subCls isEqualToString:cls]) { 524 | cursor++; 525 | } 526 | if (subview == self) { 527 | break; 528 | } 529 | } 530 | 531 | return cursor; 532 | } 533 | 534 | - (void)IQ_addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer { 535 | [self IQ_addGestureRecognizer:gestureRecognizer]; 536 | 537 | if (DebugView(NSStringFromClass([self class])) || WhiteListView(NSStringFromClass([self class]))) { 538 | return; 539 | } 540 | 541 | if ([self IQ_accessibilityIdentifier]) { 542 | /*若已经设置了id则return掉,此处可能有隐患(已经设置id的情况是addSubView在addGestureRecognizer之前) 543 | UIButton等一些控件内部也会调用这个方法*/ 544 | return; 545 | } 546 | NSLog(@"addGestureRecognizer========>%@",NSStringFromClass([self class])); 547 | 548 | /*此处仍然有问题:假如有三个UIImageView都添加了点击手势,而且是通过动态添加到一个父类里面,其id都成一样的了 549 | 解决方案:添加手势的时候,先获取到superView,然后看下当前类在superView的索引*/ 550 | 551 | /*按钮采用懒加载*/ 552 | 553 | if (![NSStringFromClass([self class]) isEqualToString:@"UIButton"] ) { 554 | /*拼接更多有用信息*/ 555 | // UIButton *btn = (UIButton *)self; 556 | // NSString *btnTitle = btn.titleLabel.text?:@""; 557 | // NSString *identiLabel = btn.accessibilityLabel; 558 | // NSString *varName = btn.accessibilityIdentifier; 559 | // contentStr = [NSString stringWithFormat:@"/Content{'var':%@,'title':%@,'tag':%ld,'label':%@}",varName,btnTitle,btn.tag,identiLabel]; 560 | 561 | /**/ 562 | NSInteger index = [self indexOfCurrentViewInSuperView:self]; 563 | NSString *identifier = [NSString stringWithFormat:@"%@%@[%ld]",kAutoSetIdentifier,NSStringFromClass([gestureRecognizer.view class]),index]; 564 | self.accessibilityIdentifier = identifier; 565 | 566 | } 567 | } 568 | 569 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 570 | /*事件传递*/ 571 | [super touchesBegan:touches withEvent:event]; 572 | /*判断是否时系统UI的touch事件,系统级的touch事件过滤,或者根据自己工程自定义*/ 573 | /*可通过设置白名单来解决*/ 574 | if (DebugView(NSStringFromClass([self class]))) { 575 | return; 576 | } 577 | 578 | if ([NSStringFromClass([self class]) isEqualToString:@"BaseBottomBarItem"]) { 579 | id target = self; 580 | IQTapTask(target); 581 | } 582 | #warning location会因为不同m屏幕分辨率而导致不准确(后续可以进行不同屏幕分辨率进行坐标系自动转换) 583 | if ([NSStringFromClass([self class]) isEqualToString:@"UIWebBrowserView"]) { 584 | // __block UITouch *firstTouch = nil; 585 | [touches enumerateObjectsUsingBlock:^(UITouch * _Nonnull obj, BOOL * _Nonnull stop) { 586 | CGPoint point = [obj locationInView:[UIApplication sharedApplication].keyWindow]; 587 | IQTapTaskWithLocation(point); 588 | *stop = YES; 589 | }]; 590 | 591 | } 592 | } 593 | 594 | @end 595 | 596 | #pragma mark--UITableView-- 597 | @implementation UITableView (IQRunTimeHook) 598 | 599 | + (void)IQHook { 600 | IQRuntimeMethodExchange([UITableView class], @selector(setDelegate:), @selector(IQ_setDelegate:)); 601 | } 602 | 603 | - (void)IQ_setDelegate:(id)delegate{ 604 | [self IQ_setDelegate:delegate]; 605 | 606 | if (DebugView(NSStringFromClass([self class]))) { 607 | return; 608 | } 609 | 610 | /*hook @selector(tableView:cellForRowAtIndexPath:)*/ 611 | if (![delegate respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) { 612 | return; 613 | } 614 | 615 | Method originMethod = class_getInstanceMethod([delegate class], @selector(tableView:cellForRowAtIndexPath:)); 616 | IMP originImp = method_getImplementation(originMethod); 617 | Method currentMethod = class_getInstanceMethod([self class], @selector(IQ_tableView:cellForRowAtIndexPath:)); 618 | IMP currentImp = method_getImplementation(currentMethod); 619 | 620 | class_addMethod([delegate class], @selector(IQ_tableView:cellForRowAtIndexPath:), currentImp, method_getTypeEncoding(currentMethod)); 621 | 622 | BOOL didAddMethod = class_addMethod([delegate class], @selector(tableView:cellForRowAtIndexPath:), currentImp, method_getTypeEncoding(currentMethod)); 623 | 624 | if (didAddMethod) { 625 | class_replaceMethod([delegate class], @selector(IQ_tableView:cellForRowAtIndexPath:), originImp, method_getTypeEncoding(originMethod)); 626 | } else { 627 | IQRuntimeMethodExchange([delegate class], @selector(tableView:cellForRowAtIndexPath:), @selector(IQ_tableView:cellForRowAtIndexPath:)); 628 | } 629 | 630 | if (![delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { 631 | return; 632 | } 633 | 634 | Method didSelectOriginMethod = class_getInstanceMethod([delegate class], @selector(tableView:didSelectRowAtIndexPath:)); 635 | IMP didSelectOriginImp = method_getImplementation(didSelectOriginMethod); 636 | Method didSelectCurrentMethod = class_getInstanceMethod([self class], @selector(IQ_tableView:didSelectRowAtIndexPath:)); 637 | IMP didSelectCurrentImp = method_getImplementation(didSelectCurrentMethod); 638 | 639 | class_addMethod([delegate class], @selector(IQ_tableView:didSelectRowAtIndexPath:), didSelectCurrentImp, method_getTypeEncoding(didSelectCurrentMethod)); 640 | 641 | BOOL didSelectdidAddMethod = class_addMethod([delegate class], @selector(tableView:didSelectRowAtIndexPath:), didSelectCurrentImp, method_getTypeEncoding(didSelectCurrentMethod)); 642 | 643 | if (didSelectdidAddMethod) { 644 | class_replaceMethod([delegate class], @selector(IQ_tableView:didSelectRowAtIndexPath:), didSelectOriginImp, method_getTypeEncoding(didSelectOriginMethod)); 645 | } else { 646 | IQRuntimeMethodExchange([delegate class], @selector(tableView:didSelectRowAtIndexPath:), @selector(IQ_tableView:didSelectRowAtIndexPath:)); 647 | } 648 | 649 | } 650 | 651 | - (void)IQ_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 652 | [self IQ_tableView:tableView didSelectRowAtIndexPath:indexPath]; 653 | if (DebugView(NSStringFromClass([self class]))) { 654 | return; 655 | } 656 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 657 | IQTapTask(cell); 658 | } 659 | 660 | - (UITableViewCell *)IQ_tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 661 | UITableViewCell *cell = [self IQ_tableView:tableView cellForRowAtIndexPath:indexPath]; 662 | if (DebugView(NSStringFromClass([self class]))) { 663 | return cell; 664 | } 665 | NSString *prefix = @""; 666 | prefix = [NSString stringWithFormat:@"%@%@/",kAutoSetIdentifier,NSStringFromClass([self class])]; 667 | 668 | NSString *reuseIdentifier = [cell reuseIdentifier]; 669 | if (!reuseIdentifier) { 670 | reuseIdentifier = NSStringFromClass([cell class]); 671 | } 672 | 673 | [cell setAccessibilityIdentifier:[NSString stringWithFormat:@"%@%@/%ld",prefix,reuseIdentifier,indexPath.row]]; 674 | 675 | return cell; 676 | } 677 | 678 | - (UIViewController*)attributeControllerWithView:(UIView *)view { 679 | for (UIView* next = view; next; next = next.superview) { 680 | UIResponder* nextResponder = [next nextResponder]; 681 | if ([nextResponder isKindOfClass:[UIViewController class]]) { 682 | return (UIViewController*)nextResponder; 683 | } 684 | } 685 | return nil; 686 | } 687 | 688 | @end 689 | 690 | #pragma mark--UICollectionView-- 691 | 692 | @implementation UICollectionView (IQRunTimeHook) 693 | 694 | + (void)IQHook { 695 | IQRuntimeMethodExchange([UICollectionView class],@selector(setDelegate:),@selector(IQ_setDelegate:)); 696 | } 697 | 698 | - (void)IQ_setDelegate:(id)delegate{ 699 | [self IQ_setDelegate:delegate]; 700 | 701 | if (DebugView(NSStringFromClass([self class]))) { 702 | return; 703 | } 704 | 705 | /*hook @selector(collectionView:cellForItemAtIndexPath:)*/ 706 | if (![delegate respondsToSelector:@selector(collectionView:cellForItemAtIndexPath:)]) { 707 | return; 708 | } 709 | 710 | Method originMethod = class_getInstanceMethod([delegate class], @selector(collectionView:cellForItemAtIndexPath:)); 711 | IMP originImp = method_getImplementation(originMethod); 712 | 713 | Method currentMethod = class_getInstanceMethod([self class], @selector(IQ_collectionView:cellForItemAtIndexPath:)); 714 | IMP currentImp = method_getImplementation(currentMethod); 715 | 716 | class_addMethod([delegate class], @selector(IQ_collectionView:cellForItemAtIndexPath:), currentImp, method_getTypeEncoding(currentMethod)); 717 | 718 | BOOL didAddMethod = class_addMethod([delegate class], @selector(collectionView:cellForItemAtIndexPath:), currentImp, method_getTypeEncoding(currentMethod)); 719 | 720 | if (didAddMethod) { 721 | class_replaceMethod([delegate class], @selector(IQ_collectionView:cellForItemAtIndexPath:), originImp, method_getTypeEncoding(originMethod)); 722 | } else { 723 | IQRuntimeMethodExchange([delegate class], 724 | @selector(collectionView:cellForItemAtIndexPath:), 725 | @selector(IQ_collectionView:cellForItemAtIndexPath:)); 726 | } 727 | 728 | if (![delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]) { 729 | return; 730 | } 731 | 732 | Method didSelectOriginMethod = class_getInstanceMethod([delegate class], @selector(collectionView:didSelectItemAtIndexPath:)); 733 | IMP didSelectOriginImp = method_getImplementation(didSelectOriginMethod); 734 | 735 | Method didSelectCurrentMethod = class_getInstanceMethod([self class], @selector(IQ_collectionView:didSelectItemAtIndexPath:)); 736 | IMP didSelectCurrentImp = method_getImplementation(didSelectCurrentMethod); 737 | 738 | class_addMethod([delegate class], @selector(IQ_collectionView:didSelectItemAtIndexPath:), didSelectCurrentImp, method_getTypeEncoding(didSelectCurrentMethod)); 739 | 740 | BOOL didSelectDidAddMethod = class_addMethod([delegate class], @selector(collectionView:didSelectItemAtIndexPath:), didSelectCurrentImp, method_getTypeEncoding(didSelectCurrentMethod)); 741 | 742 | if (didSelectDidAddMethod) { 743 | class_replaceMethod([delegate class], @selector(IQ_collectionView:didSelectItemAtIndexPath:), didSelectOriginImp, method_getTypeEncoding(didSelectOriginMethod)); 744 | } else { 745 | IQRuntimeMethodExchange([delegate class], 746 | @selector(collectionView:didSelectItemAtIndexPath:), 747 | @selector(IQ_collectionView:didSelectItemAtIndexPath:)); 748 | } 749 | 750 | } 751 | 752 | - (UICollectionViewCell *)IQ_collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 753 | UICollectionViewCell *cell = [self IQ_collectionView:collectionView cellForItemAtIndexPath:indexPath]; 754 | 755 | if (DebugView(NSStringFromClass([self class]))) { 756 | return cell; 757 | } 758 | 759 | NSString *prefix = @""; 760 | prefix = [NSString stringWithFormat:@"%@%@/",kAutoSetIdentifier,NSStringFromClass([self class])]; 761 | 762 | NSString *reuseIdentifier = [cell reuseIdentifier]; 763 | if (!reuseIdentifier) { 764 | reuseIdentifier = NSStringFromClass([cell class]); 765 | } 766 | 767 | [cell setAccessibilityIdentifier:[NSString stringWithFormat:@"%@%@/%ld",prefix,reuseIdentifier,indexPath.row]]; 768 | NSLog(@"------->%@",[cell accessibilityIdentifier]); 769 | return cell; 770 | } 771 | 772 | -(void)IQ_collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 773 | [self IQ_collectionView:collectionView didSelectItemAtIndexPath:indexPath]; 774 | if (DebugView(NSStringFromClass([self class]))) { 775 | return; 776 | } 777 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; 778 | IQTapTask(cell); 779 | } 780 | 781 | - (UIViewController*)attributeControllerWithView:(UIView *)view { 782 | for (UIView* next = view; next; next = next.superview) { 783 | UIResponder* nextResponder = [next nextResponder]; 784 | if ([nextResponder isKindOfClass:[UIViewController class]]) { 785 | return (UIViewController*)nextResponder; 786 | } 787 | } 788 | return nil; 789 | } 790 | 791 | @end 792 | 793 | #pragma mark--UIApplication-- 794 | 795 | @implementation UIApplication (IQRunTimeHook) 796 | 797 | + (void)IQHook { 798 | IQRuntimeMethodExchange([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(IQ_sendAction:to:from:forEvent:)); 799 | IQRuntimeMethodExchange([UIApplication class], @selector(sendEvent:), @selector(IQ_sendEvent:)); 800 | } 801 | 802 | - (void)IQ_sendEvent:(UIEvent *)event { 803 | [self IQ_sendEvent:event]; 804 | 805 | } 806 | 807 | - (BOOL)IQ_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { 808 | BOOL ret = [self IQ_sendAction:action to:target from:sender forEvent:event]; 809 | if (DebugView(NSStringFromClass([sender class]))) { 810 | return ret; 811 | } 812 | /*白名单,系统返回按钮已在should:popItem中拦截*/ 813 | if ([sender isKindOfClass:NSClassFromString(@"_UIButtonBarButton")]){ 814 | return ret; 815 | } 816 | 817 | IQTapTask(sender); 818 | return ret; 819 | } 820 | 821 | @end 822 | 823 | #pragma mark--IQUITestCodeMakerGenerator-- 824 | 825 | @interface IQUITestCodeMakerGenerator () 826 | 827 | @property (nonatomic, strong, readwrite) IQUITestCodeMakerFactory *factory; 828 | @property (nonatomic, strong, readwrite) GCDWebServer *webServer; 829 | 830 | @end 831 | 832 | @implementation IQUITestCodeMakerGenerator 833 | 834 | + (instancetype)sharePersistent { 835 | static dispatch_once_t onceToken; 836 | dispatch_once(&onceToken, ^{ 837 | persistent = [[IQUITestCodeMakerGenerator alloc]init]; 838 | 839 | }); 840 | return persistent; 841 | } 842 | 843 | - (void)hook { 844 | // [UIApplication IQHook]; 845 | [self aspectsHook]; 846 | [UITableView IQHook]; 847 | [UICollectionView IQHook]; 848 | [UIGestureRecognizer IQHook]; 849 | [UIView IQHook]; 850 | [UIImage IQHook]; 851 | [UINavigationController IQHook]; 852 | [UITextField IQHook]; 853 | } 854 | 855 | - (void)aspectsHook { 856 | [UIApplication aspect_hookSelector:@selector(sendAction:to:from:forEvent:) withOptions:AspectPositionAfter usingBlock:^(id info,SEL action,id target,id sender, UIEvent *event){ 857 | if (DebugView(NSStringFromClass([sender class]))) { 858 | return; 859 | } 860 | /*白名单,系统返回按钮已在should:popItem中拦截*/ 861 | if ([sender isKindOfClass:NSClassFromString(@"_UIButtonBarButton")]){ 862 | return; 863 | } 864 | IQTapTask(sender); 865 | } error:NULL]; 866 | 867 | // [UITableView aspect_hookSelector:@selector(setDelegate:) withOptions:AspectPositionAfter usingBlock:^(id info,id delegate){ 868 | // 869 | // if (![delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) { 870 | // return; 871 | // } 872 | // 873 | // [(NSObject *)delegate aspect_hookSelector:@selector(tableView:didSelectRowAtIndexPath:) withOptions:AspectPositionAfter usingBlock:^(id info,UITableView *tableView,NSIndexPath *indexPath){ 874 | // 875 | // } error:NULL]; 876 | // 877 | // if (![delegate respondsToSelector:@selector(tableView:cellForRowAtIndexPath:)]) { 878 | // return; 879 | // } 880 | // 881 | // [(NSObject *)delegate aspect_hookSelector:@selector(tableView:cellForRowAtIndexPath:) withOptions:AspectPositionAfter usingBlock:^(id info,UITableView *tableView,NSIndexPath *indexPath){ 882 | // UITableViewCell *cell = [self ]; 883 | // } error:NULL]; 884 | // 885 | // } error:NULL]; 886 | } 887 | 888 | - (void)handleApplicationWillResignActiveNotification { 889 | /*系统级弹框会触发应用willResignActive操作*/ 890 | // IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 891 | // op.eventType = IQEventResignActive; 892 | // 893 | // IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 894 | // [persistent.factory produceCodeWithOperationEvent:op]; 895 | } 896 | 897 | - (void)handleApplicationWillTerminateNotification { 898 | // IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 899 | // op.eventType = IQEventWillTerminate; 900 | // 901 | // IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 902 | // [persistent.factory produceCodeWithOperationEvent:op]; 903 | } 904 | 905 | - (void)handleApplicationDidReceiveMemoryWarningNotification { 906 | 907 | } 908 | 909 | - (void)handleConvertTaskWithIdentifier:(NSString *)identifier { 910 | if (identifier) { 911 | NSDictionary *localCap = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 912 | NSMutableDictionary *mutaleCap = [NSMutableDictionary dictionaryWithDictionary:localCap]; 913 | [mutaleCap setValue:identifier forKey:@"appiumLanguage"]; 914 | [[NSUserDefaults standardUserDefaults] setObject:mutaleCap forKey:kCapabilitiesKey]; 915 | [[NSUserDefaults standardUserDefaults] synchronize]; 916 | } 917 | 918 | IQUITestCodeMakerCapabilities *capInstance = [[IQUITestCodeMakerCapabilities alloc]init]; 919 | capInstance.driverType = IQUITestDriverAppium; 920 | 921 | IQUITestCodeMakerCapabilities *cap = [[IQUITestCodeMakerCapabilities alloc]init]; 922 | cap.driverType = IQUITestDriverAppium; 923 | 924 | [self removeAllScript]; 925 | 926 | IQUITestCodeMakerFactory *factory = [IQUITestCodeMakerFactory handleTaskUnitWithCap:cap]; 927 | factory.eventQueue = [NSMutableArray arrayWithArray:self.factory.eventQueue]; 928 | self.factory = factory; 929 | [self.factory convertEvetQueueToScript]; 930 | [self restartServer]; 931 | } 932 | 933 | - (void)handleCapChangeTaskWithKey:(NSString *)key value:(NSString *)value { 934 | NSDictionary *capLocal = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 935 | NSMutableDictionary *mutableCap = [NSMutableDictionary dictionaryWithDictionary:capLocal]; 936 | [mutableCap setValue:value forKey:key]; 937 | [[NSUserDefaults standardUserDefaults] setObject:mutableCap forKey:kCapabilitiesKey]; 938 | [[NSUserDefaults standardUserDefaults] synchronize]; 939 | /*脚本重新生成*/ 940 | [self handleConvertTaskWithIdentifier:nil]; 941 | } 942 | 943 | - (void)handleRecordControlEventWithState:(BOOL)state { 944 | if (state) { 945 | /*开启,移除本地脚本缓存*/ 946 | [self removeAllScript]; 947 | 948 | IQUITestCodeMakerCapabilities *capInstance = [[IQUITestCodeMakerCapabilities alloc]init]; 949 | capInstance.driverType = IQUITestDriverAppium; 950 | 951 | IQUITestCodeMakerCapabilities *cap = [[IQUITestCodeMakerCapabilities alloc]init]; 952 | cap.driverType = IQUITestDriverAppium; 953 | 954 | IQUITestCodeMakerFactory *factory = [IQUITestCodeMakerFactory handleTaskUnitWithCap:cap]; 955 | self.factory = factory; 956 | } else { 957 | IQUITestOperationEvent *lastEvent = [self.factory.eventQueue lastObject]; 958 | if (!lastEvent || lastEvent.eventType == IQEventEndCode) { 959 | return; 960 | } 961 | /*结束录制*/ 962 | IQUITestOperationEvent *op = [IQUITestOperationEvent new]; 963 | op.eventType = IQEventEndCode; 964 | [self.factory produceCodeWithOperationEvent:op]; 965 | } 966 | } 967 | 968 | - (void)handleApplicationDidFinishLaunching { 969 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 970 | IQUITestDebugBall *debugBall = [[IQUITestDebugBall alloc]initWithFrame:CGRectMake([[UIScreen mainScreen] bounds].size.width/2 - 40, 20, 80, 80)]; 971 | [keyWindow addSubview:debugBall]; 972 | } 973 | 974 | #pragma mark--GCDWebServer-- 975 | - (void)webServerDidStart:(GCDWebServer*)server { 976 | 977 | } 978 | 979 | - (void)removeAllScript { 980 | NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); 981 | NSString *documentDirectory = [directoryPaths objectAtIndex:0]; 982 | NSString *scriptDir = [documentDirectory stringByAppendingString:@"/IQScripts"]; 983 | [[NSFileManager defaultManager] removeItemAtPath:scriptDir error:NULL]; 984 | } 985 | 986 | - (void)restartServer { 987 | [_webServer stop]; 988 | [_webServer removeAllHandlers]; 989 | [_webServer addGETHandlerForPath:@"/" filePath:_factory.scriptPath isAttachment:NO cacheAge:2 allowRangeRequests:YES]; 990 | [_webServer start]; 991 | } 992 | 993 | #pragma mark--Getters & Setters-- 994 | - (IQUITestCodeMakerFactory *)factory { 995 | if (!_factory) { 996 | [self removeAllScript]; 997 | _factory = [IQUITestCodeMakerFactory handleTaskUnit]; 998 | } 999 | return _factory; 1000 | } 1001 | 1002 | - (GCDWebServer *)webServer { 1003 | if (!_webServer){ 1004 | _webServer = [[GCDWebServer alloc]init]; 1005 | _webServer.delegate = self; 1006 | [_webServer addGETHandlerForPath:@"/" filePath:self.factory.scriptPath isAttachment:NO cacheAge:2 allowRangeRequests:YES]; 1007 | } 1008 | return _webServer; 1009 | } 1010 | 1011 | @end 1012 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJSPromiseUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJSPromiseUnit.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/9. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | 11 | @interface IQUITestCodeMakerJSPromiseUnit : IQUITestCodeMakerFactory 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJSPromiseUnit.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJSPromiseUnit.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/9. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerJSPromiseUnit.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @implementation IQUITestCodeMakerJSPromiseUnit 14 | 15 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 16 | [self produceTemplateCodeOnce]; 17 | switch (op.eventType) { 18 | case IQUIEventUnknown: 19 | { 20 | /*未知*/ 21 | } 22 | break; 23 | case IQUIEventTap: 24 | { 25 | [self produceTapCodeWithOperationEvent:op]; 26 | } 27 | break; 28 | case IQUIEventDoubleTap: 29 | { 30 | 31 | } 32 | break; 33 | case IQUIEventSwipe: 34 | { 35 | 36 | } 37 | break; 38 | case IQUIEventPinch: 39 | { 40 | 41 | } 42 | break; 43 | case IQUIEventZoom: 44 | { 45 | 46 | } 47 | break; 48 | case IQUIEventLongPress: 49 | { 50 | 51 | } 52 | break; 53 | case IQUIEventSendKey: 54 | { 55 | [self produceSendKeyCodeWithOperationEvent:op]; 56 | } 57 | break; 58 | 59 | case IQEventEndCode: 60 | { 61 | [self produceEndCodeOnce]; 62 | } 63 | break; 64 | default: 65 | break; 66 | } 67 | } 68 | 69 | - (void)produceTapCodeWithOperationEvent:(IQUITestOperationEvent *)op { 70 | if (!self.isConverting) { 71 | [self.eventQueue addObject:op]; 72 | } 73 | self.eventIndex++; 74 | NSString *tapCode = [NSString stringWithFormat:@"\n\ 75 | let el%ld = await driver.elementByAccessibilityId(\"%@\");\n\ 76 | await el%ld.click()\n",self.eventIndex,op.identifier,self.eventIndex]; 77 | [self storeProductCode:tapCode]; 78 | } 79 | 80 | - (void)produceSendKeyCodeWithOperationEvent:(IQUITestOperationEvent *)op { 81 | if (!self.isConverting) { 82 | [self.eventQueue addObject:op]; 83 | } 84 | self.eventIndex++; 85 | NSString *sendKeyCode = [NSString stringWithFormat:@"\n\ 86 | let el%ld = await driver.elementByAccessibilityId(\"%@\");\n\ 87 | await el%ld.type(\"%@\")\n",self.eventIndex,op.identifier,self.eventIndex,op.value]; 88 | [self storeProductCode:sendKeyCode]; 89 | } 90 | 91 | - (void)produceTemplateCodeOnce { 92 | if (!self.templateCodeFlag) { 93 | [self templateCode]; 94 | } 95 | self.templateCodeFlag++; 96 | } 97 | 98 | - (void)templateCode { 99 | if (!self.isConverting) { 100 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 101 | event.eventType = IQEventTemplateCode; 102 | [self.eventQueue addObject:event]; 103 | } 104 | NSString *code = [NSString stringWithFormat:@"\n\ 105 | /*IQ UITest Code Maker.Rquire Appium Version(%@)*/\n\ 106 | /*This Sample Code Uses admc/wd Client Library*/\n\ 107 | /*Install It With Cmd 'npm install wd'*/\n\ 108 | \n\ 109 | \n\ 110 | const wd = require('wd');\n\ 111 | const caps = {\"platformName\":\"%@\",\"platformVersion\":\"%@\",\"deviceName\":\"%@\",\"automationName\":\"%@\",\"app\":\"%@\"};\n ", 112 | self.cap.appiumCap.appiumVersion,self.cap.appiumCap.platformName,self.cap.appiumCap.platformVersion,self.cap.appiumCap.deviceName,self.cap.appiumCap.automationName,self.cap.appiumCap.app]; 113 | 114 | code = [code stringByAppendingFormat:@"\n\ 115 | \n\ 116 | const driver = wd.promiseChainRemote(\"http://%@:%@/wd/hub\");\n\ 117 | \n\ 118 | \n\ 119 | async function main () {\n\ 120 | await driver.init(caps);", self.cap.appiumCap.serverAddress, self.cap.appiumCap.serverPort]; 121 | 122 | [[NSFileManager defaultManager] removeItemAtPath:self.scriptPath error:nil]; 123 | [self storeProductCode:code]; 124 | } 125 | 126 | - (void)produceEndCodeOnce { 127 | if (!self.endCodeFlag) { 128 | [self endCode]; 129 | } 130 | self.endCodeFlag++; 131 | } 132 | 133 | - (void)endCode { 134 | if (!self.isConverting) { 135 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 136 | event.eventType = IQEventEndCode; 137 | [self.eventQueue addObject:event]; 138 | } 139 | NSString *code = @"\n\ 140 | await driver.quit();\n\ 141 | }\n\ 142 | main().catch(console.log);\n"; 143 | [self storeProductCode:code]; 144 | } 145 | 146 | - (void)convertEvetQueueToScript { 147 | self.isConverting = YES; 148 | for (IQUITestOperationEvent *op in self.eventQueue) { 149 | if (op.eventType == IQEventTemplateCode) { 150 | [self produceTemplateCodeOnce]; 151 | } else if (op.eventType == IQEventEndCode) { 152 | [self produceEndCodeOnce]; 153 | } else { 154 | [self produceCodeWithOperationEvent:op]; 155 | } 156 | } 157 | self.isConverting = NO; 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJSWDUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJSWDUnit.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | 11 | @interface IQUITestCodeMakerJSWDUnit : IQUITestCodeMakerFactory 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJSWDUnit.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJSWDUnit.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerJSWDUnit.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @implementation IQUITestCodeMakerJSWDUnit 14 | 15 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 16 | [self produceTemplateCodeOnce]; 17 | switch (op.eventType) { 18 | case IQUIEventUnknown: 19 | { 20 | /*未知*/ 21 | } 22 | break; 23 | case IQUIEventTap: 24 | { 25 | [self produceTapCodeWithOperationEvent:op]; 26 | } 27 | break; 28 | case IQUIEventDoubleTap: 29 | { 30 | 31 | } 32 | break; 33 | case IQUIEventSwipe: 34 | { 35 | 36 | } 37 | break; 38 | case IQUIEventPinch: 39 | { 40 | 41 | } 42 | break; 43 | case IQUIEventZoom: 44 | { 45 | 46 | } 47 | break; 48 | case IQUIEventLongPress: 49 | { 50 | 51 | } 52 | break; 53 | case IQUIEventSendKey: 54 | { 55 | [self produceSendKeyCodeWithOperationEvent:op]; 56 | } 57 | break; 58 | case IQEventEndCode: 59 | { 60 | [self produceEndCodeOnce]; 61 | } 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | - (void)produceTapCodeWithOperationEvent:(IQUITestOperationEvent *)op { 69 | if (!self.isConverting) { 70 | [self.eventQueue addObject:op]; 71 | } 72 | self.eventIndex++; 73 | NSString *tapCode = [NSString stringWithFormat:@"\n\ 74 | .element(\"~%@\")\n\ 75 | .click()\n",op.identifier]; 76 | [self storeProductCode:tapCode]; 77 | } 78 | 79 | - (void)produceSendKeyCodeWithOperationEvent:(IQUITestOperationEvent *)op { 80 | if (!self.isConverting) { 81 | [self.eventQueue addObject:op]; 82 | } 83 | self.eventIndex++; 84 | NSString *sendKeyCode = [NSString stringWithFormat:@"\n\ 85 | .element(\"~%@\")\n\ 86 | .sendKeys(\"%@\")\n",op.identifier,op.value]; 87 | [self storeProductCode:sendKeyCode]; 88 | } 89 | 90 | - (void)produceTemplateCodeOnce { 91 | if (!self.templateCodeFlag) { 92 | [self templateCode]; 93 | } 94 | self.templateCodeFlag++; 95 | } 96 | 97 | - (void)templateCode { 98 | if (!self.isConverting) { 99 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 100 | event.eventType = IQEventTemplateCode; 101 | [self.eventQueue addObject:event]; 102 | } 103 | NSString *code = [NSString stringWithFormat:@"\n\ 104 | /*IQ UITest Code Maker.Rquire Appium Version(%@)*/\n\ 105 | /*This Sample Code Uses The Webdriverio Client*/\n\ 106 | /*Install It With Cmd 'npm install webdriverio'*/\n\ 107 | \n\ 108 | \n\ 109 | const wdio = require('webdriverio');\n\ 110 | const caps = {\"platformName\":\"%@\",\"platformVersion\":\"%@\",\"deviceName\":\"%@\",\"automationName\":\"%@\",\"app\":\"%@\"};\n ", 111 | self.cap.appiumCap.appiumVersion,self.cap.appiumCap.platformName,self.cap.appiumCap.platformVersion,self.cap.appiumCap.deviceName,self.cap.appiumCap.automationName,self.cap.appiumCap.app]; 112 | 113 | code = [code stringByAppendingFormat:@"\n\ 114 | \n\ 115 | const driver = wdio.remote({protocol: \"http\",host: \"%@\",port: %@,path: \"/wd/hub\",desiredCapabilities: caps});\n\ 116 | \n\ 117 | \ndriver.init()", self.cap.appiumCap.serverAddress, self.cap.appiumCap.serverPort]; 118 | 119 | [[NSFileManager defaultManager] removeItemAtPath:self.scriptPath error:nil]; 120 | [self storeProductCode:code]; 121 | } 122 | 123 | - (void)produceEndCodeOnce { 124 | if (!self.endCodeFlag) { 125 | [self endCode]; 126 | } 127 | self.endCodeFlag++; 128 | } 129 | 130 | - (void)endCode { 131 | if (!self.isConverting) { 132 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 133 | event.eventType = IQEventEndCode; 134 | [self.eventQueue addObject:event]; 135 | } 136 | NSString *code = @"\n\ 137 | .end()\n"; 138 | [self storeProductCode:code]; 139 | } 140 | 141 | - (void)convertEvetQueueToScript { 142 | self.isConverting = YES; 143 | for (IQUITestOperationEvent *op in self.eventQueue) { 144 | if (op.eventType == IQEventTemplateCode) { 145 | [self produceTemplateCodeOnce]; 146 | } else if (op.eventType == IQEventEndCode) { 147 | [self produceEndCodeOnce]; 148 | } else { 149 | [self produceCodeWithOperationEvent:op]; 150 | } 151 | } 152 | self.isConverting = NO; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJavaUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJavaUnit.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/13. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | 11 | @interface IQUITestCodeMakerJavaUnit : IQUITestCodeMakerFactory 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerJavaUnit.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerJavaUnit.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/13. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerJavaUnit.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @implementation IQUITestCodeMakerJavaUnit 14 | 15 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 16 | [self produceTemplateCodeOnce]; 17 | switch (op.eventType) { 18 | case IQUIEventUnknown: 19 | { 20 | /*未知*/ 21 | } 22 | break; 23 | case IQUIEventTap: 24 | { 25 | [self produceTapCodeWithOperationEvent:op]; 26 | } 27 | break; 28 | case IQUIEventDoubleTap: 29 | { 30 | 31 | } 32 | break; 33 | case IQUIEventSwipe: 34 | { 35 | 36 | } 37 | break; 38 | case IQUIEventPinch: 39 | { 40 | 41 | } 42 | break; 43 | case IQUIEventZoom: 44 | { 45 | 46 | } 47 | break; 48 | case IQUIEventLongPress: 49 | { 50 | 51 | } 52 | break; 53 | case IQUIEventSendKey: 54 | { 55 | [self produceSendKeyCodeWithOperationEvent:op]; 56 | } 57 | break; 58 | case IQEventEndCode: 59 | { 60 | [self produceEndCodeOnce]; 61 | } 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | - (void)produceTapCodeWithOperationEvent:(IQUITestOperationEvent *)op { 69 | if (!self.isConverting) { 70 | [self.eventQueue addObject:op]; 71 | } 72 | self.eventIndex++; 73 | NSString *tapCode = [NSString stringWithFormat:@"\n\ 74 | MobileElement el%ld = (MobileElement) driver.findElementByAccessibilityId(\"%@\");\n\ 75 | el%ld.click();\n",self.eventIndex,op.identifier,self.eventIndex]; 76 | [self storeProductCode:tapCode]; 77 | } 78 | 79 | - (void)produceSendKeyCodeWithOperationEvent:(IQUITestOperationEvent *)op { 80 | if (!self.isConverting) { 81 | [self.eventQueue addObject:op]; 82 | } 83 | self.eventIndex++; 84 | NSString *sendCode = [NSString stringWithFormat:@"\n\ 85 | MobileElement el%ld = (MobileElement) driver.findElementByAccessibilityId(\"%@\");\n\ 86 | el%ld.sendKeys(\"%@\");\n",self.eventIndex,op.identifier,self.eventIndex,op.value]; 87 | [self storeProductCode:sendCode]; 88 | } 89 | 90 | - (void)produceTemplateCodeOnce { 91 | if (!self.templateCodeFlag) { 92 | [self templateCode]; 93 | } 94 | self.templateCodeFlag++; 95 | } 96 | 97 | - (void)templateCode { 98 | if (!self.isConverting) { 99 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 100 | event.eventType = IQEventTemplateCode; 101 | [self.eventQueue addObject:event]; 102 | } 103 | NSString *code = [NSString stringWithFormat:@"\n\ 104 | /*IQ UITest Code Maker.Rquire Appium Version(%@)*/\n\ 105 | /*This Sample Code Uses JUnit*/\n\ 106 | \n\ 107 | \n\ 108 | import io.appium.java_client.MobileElement;\n\ 109 | import io.appium.java_client.ios.IOSDriver;\n\ 110 | import junit.framework.TestCase;\n\ 111 | import org.junit.After;\n\ 112 | import org.junit.Before;\n\ 113 | import org.junit.Test;\n\ 114 | import java.net.MalformedURLException;\n\ 115 | import java.net.URL;\n\ 116 | import org.openqa.selenium.remote.DesiredCapabilities;\n\ 117 | \n\ 118 | public class SampleTest {\n\ 119 | private IOSDriver driver;\n\ 120 | \n\ 121 | @Before\n\ 122 | public void setUp() throws MalformedURLException {\n\ 123 | DesiredCapabilities desiredCapabilities = new DesiredCapabilities();\n\ 124 | desiredCapabilities.setCapability(\"platformName\", \"%@\");\n\ 125 | desiredCapabilities.setCapability(\"platformVersion\", \"%@\");\n\ 126 | desiredCapabilities.setCapability(\"deviceName\", \"%@\");\n\ 127 | desiredCapabilities.setCapability(\"automationName\", \"%@\");\n\ 128 | desiredCapabilities.setCapability(\"app\", \"%@\");\n\ 129 | URL remoteUrl = new URL(\"http://%@:%@/wd/hub\");\n\ 130 | driver = new IOSDriver(remoteUrl, desiredCapabilities);\n\ 131 | }\n\ 132 | \n\ 133 | @Test\n\ 134 | public void sampleTest() {\n", self.cap.appiumCap.appiumVersion,self.cap.appiumCap.platformName,self.cap.appiumCap.platformVersion,self.cap.appiumCap.deviceName,self.cap.appiumCap.automationName,self.cap.appiumCap.app,self.cap.appiumCap.serverAddress, self.cap.appiumCap.serverPort]; 135 | 136 | [[NSFileManager defaultManager] removeItemAtPath:self.scriptPath error:nil]; 137 | [self storeProductCode:code]; 138 | } 139 | 140 | - (void)produceEndCodeOnce { 141 | if (!self.endCodeFlag) { 142 | [self endCode]; 143 | } 144 | self.endCodeFlag++; 145 | } 146 | 147 | - (void)endCode { 148 | if (!self.isConverting) { 149 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 150 | event.eventType = IQEventEndCode; 151 | [self.eventQueue addObject:event]; 152 | } 153 | NSString *code = @"\n\ 154 | }\n\ 155 | \n\ 156 | @After\n\ 157 | public void tearDown() {\n\ 158 | driver.quit();\n\ 159 | }\n"; 160 | [self storeProductCode:code]; 161 | } 162 | 163 | - (void)convertEvetQueueToScript { 164 | self.isConverting = YES; 165 | for (IQUITestOperationEvent *op in self.eventQueue) { 166 | if (op.eventType == IQEventTemplateCode) { 167 | [self produceTemplateCodeOnce]; 168 | } else if (op.eventType == IQEventEndCode) { 169 | [self produceEndCodeOnce]; 170 | } else { 171 | [self produceCodeWithOperationEvent:op]; 172 | } 173 | } 174 | self.isConverting = NO; 175 | } 176 | 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerPythonUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerPythonUnit.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | 11 | @interface IQUITestCodeMakerPythonUnit : IQUITestCodeMakerFactory 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerPythonUnit.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerPythonUnit.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerPythonUnit.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @implementation IQUITestCodeMakerPythonUnit 14 | 15 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 16 | [self produceTemplateCodeOnce]; 17 | switch (op.eventType) { 18 | case IQUIEventUnknown: 19 | { 20 | /*未知*/ 21 | } 22 | break; 23 | case IQUIEventTap: 24 | { 25 | [self produceTapCodeWithOperationEvent:op]; 26 | } 27 | break; 28 | case IQUIEventDoubleTap: 29 | { 30 | 31 | } 32 | break; 33 | case IQUIEventSwipe: 34 | { 35 | 36 | } 37 | break; 38 | case IQUIEventPinch: 39 | { 40 | 41 | } 42 | break; 43 | case IQUIEventZoom: 44 | { 45 | 46 | } 47 | break; 48 | case IQUIEventLongPress: 49 | { 50 | 51 | } 52 | break; 53 | case IQUIEventSendKey: 54 | { 55 | [self produceSendKeyCodeWithOperationEvent:op]; 56 | } 57 | break; 58 | case IQEventEndCode: 59 | { 60 | [self produceEndCodeOnce]; 61 | } 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | - (void)produceTapCodeWithOperationEvent:(IQUITestOperationEvent *)op { 69 | if (!self.isConverting) { 70 | [self.eventQueue addObject:op]; 71 | } 72 | self.eventIndex++; 73 | NSString *tapCode = [NSString stringWithFormat:@"el%ld = driver.find_element_by_accessibility_id(\"%@\")\nel%ld.click()\n\n",self.eventIndex,op.identifier,self.eventIndex]; 74 | [self storeProductCode:tapCode]; 75 | } 76 | 77 | - (void)produceSendKeyCodeWithOperationEvent:(IQUITestOperationEvent *)op { 78 | if (!self.isConverting) { 79 | [self.eventQueue addObject:op]; 80 | } 81 | self.eventIndex++; 82 | NSString *sendKeyCode = [NSString stringWithFormat:@"el%ld = driver.find_element_by_accessibility_id(\"%@\")\nel%ld.send_keys('%@')\n\n",self.eventIndex,op.identifier,self.eventIndex,op.value]; 83 | [self storeProductCode:sendKeyCode]; 84 | } 85 | 86 | - (void)produceTemplateCodeOnce { 87 | if (!self.templateCodeFlag) { 88 | [self templateCode]; 89 | } 90 | self.templateCodeFlag++; 91 | } 92 | 93 | - (void)templateCode { 94 | if (!self.isConverting) { 95 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 96 | event.eventType = IQEventTemplateCode; 97 | [self.eventQueue addObject:event]; 98 | } 99 | NSString *code = [NSString stringWithFormat:@"\n\ 100 | #coding=utf-8\n\ 101 | #IQ UITest Code Maker.Rquire Appium Version(%@)#\n\ 102 | #This Sample Code Uses The Appium Python Client#\n\ 103 | #Install It With Cmd 'pip install Appium-Python-Client'#\n\ 104 | \n\ 105 | \n\ 106 | from appium import webdriver\n\ 107 | caps = {}\n\ 108 | caps[\"platformName\"] = \"%@\"\n\ 109 | caps[\"platformVersion\"] = \"%@\"\n\ 110 | caps[\"deviceName\"] = \"%@\"\n\ 111 | caps[\"automationName\"] = \"%@\"\n\ 112 | caps[\"app\"] = \"%@\"\n ", 113 | self.cap.appiumCap.appiumVersion,self.cap.appiumCap.platformName,self.cap.appiumCap.platformVersion,self.cap.appiumCap.deviceName,self.cap.appiumCap.automationName,self.cap.appiumCap.app]; 114 | 115 | code = [code stringByAppendingFormat:@"\n\ 116 | \n\ 117 | driver = webdriver.Remote(\"http://%@:%@/wd/hub\", caps)\n\ 118 | \n", self.cap.appiumCap.serverAddress, self.cap.appiumCap.serverPort]; 119 | 120 | [[NSFileManager defaultManager] removeItemAtPath:self.scriptPath error:nil]; 121 | [self storeProductCode:code]; 122 | } 123 | 124 | - (void)produceEndCodeOnce { 125 | if (!self.endCodeFlag) { 126 | [self endCode]; 127 | } 128 | self.endCodeFlag++; 129 | } 130 | 131 | - (void)endCode { 132 | if (!self.isConverting) { 133 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 134 | event.eventType = IQEventEndCode; 135 | [self.eventQueue addObject:event]; 136 | } 137 | NSString *code = @"\ndriver.quit()\n"; 138 | [self storeProductCode:code]; 139 | } 140 | 141 | - (void)convertEvetQueueToScript { 142 | self.isConverting = YES; 143 | for (IQUITestOperationEvent *op in self.eventQueue) { 144 | if (op.eventType == IQEventTemplateCode) { 145 | [self produceTemplateCodeOnce]; 146 | } else if (op.eventType == IQEventEndCode) { 147 | [self produceEndCodeOnce]; 148 | } else { 149 | [self produceCodeWithOperationEvent:op]; 150 | } 151 | } 152 | self.isConverting = NO; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerRubyUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerRubyUnit.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerFactory.h" 10 | 11 | @interface IQUITestCodeMakerRubyUnit : IQUITestCodeMakerFactory 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestCodeMakerRubyUnit.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestCodeMakerRubyUnit.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestCodeMakerRubyUnit.h" 10 | #import "IQUITestCodeMakerCapabilities.h" 11 | #import "IQUITestOperationEvent.h" 12 | 13 | @implementation IQUITestCodeMakerRubyUnit 14 | 15 | - (void)produceCodeWithOperationEvent:(IQUITestOperationEvent *)op { 16 | [self produceTemplateCodeOnce]; 17 | switch (op.eventType) { 18 | case IQUIEventUnknown: 19 | { 20 | /*未知*/ 21 | } 22 | break; 23 | case IQUIEventTap: 24 | { 25 | [self produceTapCodeWithOperationEvent:op]; 26 | } 27 | break; 28 | case IQUIEventDoubleTap: 29 | { 30 | 31 | } 32 | break; 33 | case IQUIEventSwipe: 34 | { 35 | 36 | } 37 | break; 38 | case IQUIEventPinch: 39 | { 40 | 41 | } 42 | break; 43 | case IQUIEventZoom: 44 | { 45 | 46 | } 47 | break; 48 | case IQUIEventLongPress: 49 | { 50 | 51 | } 52 | break; 53 | case IQUIEventSendKey: 54 | { 55 | [self produceSendKeyCodeWithOperationEvent:op]; 56 | } 57 | break; 58 | case IQEventEndCode: 59 | { 60 | [self produceEndCodeOnce]; 61 | } 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | - (void)produceTapCodeWithOperationEvent:(IQUITestOperationEvent *)op { 69 | if (!self.isConverting) { 70 | [self.eventQueue addObject:op]; 71 | } 72 | 73 | if (op.locateStrategy == IQElementLocateByCoordinate) { 74 | /*坐标*/ 75 | [self produceTapCodeUsingLocateStrategyWithOperationEvent:op]; 76 | return; 77 | } 78 | 79 | /*identifier*/ 80 | self.eventIndex++; 81 | NSString *tapCode = [NSString stringWithFormat:@"el%ld = driver.find_element(:accessibility_id, \"%@\")\nel%ld.click\n\n",self.eventIndex,op.identifier,self.eventIndex]; 82 | [self storeProductCode:tapCode]; 83 | } 84 | 85 | - (void)produceTapCodeUsingLocateStrategyWithOperationEvent:(IQUITestOperationEvent *)op { 86 | NSString *tapCode = [NSString stringWithFormat:@"Appium::TouchAction.new.tap(x: %f, y: %f).perform\n\n",op.touchPoint.x,op.touchPoint.y]; 87 | [self storeProductCode:tapCode]; 88 | } 89 | 90 | - (void)produceSendKeyCodeWithOperationEvent:(IQUITestOperationEvent *)op { 91 | if (!self.isConverting) { 92 | [self.eventQueue addObject:op]; 93 | } 94 | self.eventIndex++; 95 | NSString *sendKeyCode = [NSString stringWithFormat:@"el%ld = driver.find_element(:accessibility_id, \"%@\")\nel%ld.send_keys '%@'\n\n",self.eventIndex,op.identifier,self.eventIndex,op.value]; 96 | [self storeProductCode:sendKeyCode]; 97 | } 98 | 99 | - (void)produceTemplateCodeOnce { 100 | if (!self.templateCodeFlag) { 101 | [self templateCode]; 102 | } 103 | self.templateCodeFlag++; 104 | } 105 | 106 | - (void)templateCode { 107 | if (!self.isConverting) { 108 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 109 | event.eventType = IQEventTemplateCode; 110 | [self.eventQueue addObject:event]; 111 | } 112 | 113 | NSString *code = [NSString stringWithFormat:@"\n\ 114 | #IQ UITest Code Maker.Rquire Appium Version(%@)#\n\ 115 | #This Sample Code Uses The Appium Ruby Client#\n\ 116 | #Install It With Cmd 'gem install appium_lib'#\n\ 117 | #Require appium_lib Version(9.14.3),Require Ruby Version >= 2.2 .#\n\ 118 | \n\ 119 | \n\ 120 | require 'rubygems'\n\ 121 | require 'appium_lib'\n\ 122 | caps = {}\n\ 123 | caps[\"platformName\"] = \"%@\"\n\ 124 | caps[\"platformVersion\"] = \"%@\"\n\ 125 | caps[\"deviceName\"] = \"%@\"\n\ 126 | caps[\"automationName\"] = \"%@\"\n\ 127 | caps[\"app\"] = \"%@\"\n ", 128 | self.cap.appiumCap.appiumVersion,self.cap.appiumCap.platformName,self.cap.appiumCap.platformVersion,self.cap.appiumCap.deviceName,self.cap.appiumCap.automationName,self.cap.appiumCap.app]; 129 | 130 | code = [code stringByAppendingFormat:@"\n\ 131 | \n\ 132 | opts = {\n\ 133 | \tsauce_username: nil,\n\ 134 | \tserver_url: \"http://%@:%@/wd/hub\"\n\ 135 | }\n\ 136 | driver = Appium::Driver.new({caps: caps, appium_lib: opts}).start_driver\n\ 137 | \n", self.cap.appiumCap.serverAddress, self.cap.appiumCap.serverPort]; 138 | 139 | [[NSFileManager defaultManager] removeItemAtPath:self.scriptPath error:nil]; 140 | [self storeProductCode:code]; 141 | } 142 | 143 | - (void)produceEndCodeOnce { 144 | if (!self.endCodeFlag) { 145 | [self endCode]; 146 | } 147 | self.endCodeFlag++; 148 | } 149 | 150 | - (void)endCode { 151 | if (!self.isConverting) { 152 | IQUITestOperationEvent *event = [IQUITestOperationEvent new]; 153 | event.eventType = IQEventEndCode; 154 | [self.eventQueue addObject:event]; 155 | } 156 | NSString *code = @"\ndriver.quit\n"; 157 | [self storeProductCode:code]; 158 | } 159 | 160 | - (void)convertEvetQueueToScript { 161 | self.isConverting = YES; 162 | for (IQUITestOperationEvent *op in self.eventQueue) { 163 | if (op.eventType == IQEventTemplateCode) { 164 | [self produceTemplateCodeOnce]; 165 | } else if (op.eventType == IQEventEndCode) { 166 | [self produceEndCodeOnce]; 167 | } else { 168 | [self produceCodeWithOperationEvent:op]; 169 | } 170 | } 171 | self.isConverting = NO; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugBall.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugBall.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface IQUITestDebugBall : UIButton 12 | 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugBall.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugBall.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugBall.h" 10 | #import "IQUITestDebugTable.h" 11 | 12 | @interface IQUITestDebugBall () 13 | 14 | @property (nonatomic, strong) UIPanGestureRecognizer *pan; 15 | @property (nonatomic, strong) IQUITestDebugTable *debugTable; 16 | 17 | @end 18 | 19 | @implementation IQUITestDebugBall 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame { 22 | if (self = [super initWithFrame:frame]) { 23 | [self addTarget:self action:@selector(debugBallTap) forControlEvents:UIControlEventTouchUpInside]; 24 | [self addGestureRecognizer:self.pan]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)debugBallTap { 30 | self.hidden = YES; 31 | __weak typeof(self)weakSelf = self; 32 | [self.debugTable showDebugView]; 33 | self.debugTable.debugBlock = ^{ 34 | weakSelf.hidden = NO; 35 | }; 36 | } 37 | 38 | - (void)dragMove:(UIPanGestureRecognizer *)panGestureRecognizer 39 | { 40 | if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { 41 | CGPoint translation = [panGestureRecognizer translationInView:self]; 42 | CGFloat stopX = (self.center.x + translation.x); 43 | CGFloat stopY = (self.center.y + translation.y); 44 | if (stopX <= 40 || stopX >= ([[UIScreen mainScreen] bounds].size.width - 40) || stopY <=40 || stopY>= ([[UIScreen mainScreen] bounds].size.height - 40)) { 45 | return; 46 | } 47 | self.center = CGPointMake(stopX, stopY); 48 | [panGestureRecognizer setTranslation:CGPointZero inView:self]; 49 | } 50 | } 51 | 52 | - (void)drawRect:(CGRect)rect { 53 | self.layer.backgroundColor = [UIColor clearColor].CGColor; 54 | CAShapeLayer *pulseLayer = [CAShapeLayer layer]; 55 | pulseLayer.frame = self.layer.bounds; 56 | pulseLayer.path = [UIBezierPath bezierPathWithOvalInRect:pulseLayer.bounds].CGPath; 57 | pulseLayer.fillColor = [UIColor purpleColor].CGColor; 58 | pulseLayer.opacity = 0.0; 59 | 60 | CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer]; 61 | replicatorLayer.frame = self.bounds; 62 | replicatorLayer.instanceCount = 4; 63 | replicatorLayer.instanceDelay = 1; 64 | [replicatorLayer addSublayer:pulseLayer]; 65 | [self.layer addSublayer:replicatorLayer]; 66 | 67 | CABasicAnimation *opacityAnima = [CABasicAnimation animationWithKeyPath:@"opacity"]; 68 | opacityAnima.fromValue = @(0.3); 69 | opacityAnima.toValue = @(0.0); 70 | 71 | CABasicAnimation *scaleAnima = [CABasicAnimation animationWithKeyPath:@"transform"]; 72 | scaleAnima.fromValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DIdentity, 0.0, 0.0, 0.0)]; 73 | scaleAnima.toValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DIdentity, 1.0, 1.0, 0.0)]; 74 | 75 | CAAnimationGroup *groupAnima = [CAAnimationGroup animation]; 76 | groupAnima.animations = @[opacityAnima, scaleAnima]; 77 | groupAnima.duration = 4.0; 78 | groupAnima.autoreverses = NO; 79 | groupAnima.repeatCount = HUGE; 80 | [pulseLayer addAnimation:groupAnima forKey:@"groupAnimation"]; 81 | } 82 | 83 | #pragma mark--Getters & Setters-- 84 | - (UIPanGestureRecognizer *)pan { 85 | if (!_pan) { 86 | _pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(dragMove:)]; 87 | 88 | } 89 | return _pan; 90 | } 91 | 92 | - (IQUITestDebugTable *)debugTable { 93 | if (!_debugTable) { 94 | _debugTable = [[IQUITestDebugTable alloc]initWithFrame:[[UIScreen mainScreen] bounds]]; 95 | [[UIApplication sharedApplication].keyWindow addSubview:_debugTable]; 96 | } 97 | return _debugTable; 98 | } 99 | @end 100 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugKeyValueCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugKeyValueCell.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IQUITestDebugKeyValueModel.h" 11 | @class IQUITestDebugKVModel; 12 | 13 | @protocol IQUITestDebugKeyValueDelegate 14 | 15 | - (void)capMapHasChanged; 16 | 17 | @end 18 | 19 | @interface IQUITestDebugKeyValueCell : UITableViewCell 20 | 21 | @property (nonatomic, weak) iddelegate; 22 | 23 | - (void)updateViewWithViewModel:(IQUITestDebugKVModel *)viewModel; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugKeyValueCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugKeyValueCell.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugKeyValueCell.h" 10 | 11 | static NSString *const kMobileConfigUrl = @"https://www.pgyer.com/udid"; 12 | 13 | @interface IQUIRightBarButton : UIButton 14 | 15 | @end 16 | 17 | @implementation IQUIRightBarButton 18 | 19 | 20 | @end 21 | 22 | @interface IQUITestDebugKeyValueCell () 23 | 24 | @property (nonatomic, strong) UILabel *titleLabel; 25 | @property (nonatomic, strong) UITextField *textFiled; 26 | @property (nonatomic, strong) IQUITestDebugKVModel *kvModel; 27 | @property (nonatomic, strong) IQUIRightBarButton *rightBarButton; 28 | 29 | @end 30 | 31 | @implementation IQUITestDebugKeyValueCell 32 | 33 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 34 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 35 | [self setUpSubviews]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)setUpSubviews { 41 | self.backgroundColor = [UIColor clearColor]; 42 | [self.contentView addSubview:self.titleLabel]; 43 | [self.contentView addSubview:self.textFiled]; 44 | [self.contentView addSubview:self.rightBarButton]; 45 | } 46 | 47 | - (void)updateViewWithViewModel:(IQUITestDebugKVModel *)viewModel { 48 | self.kvModel = viewModel; 49 | self.titleLabel.text = viewModel.title; 50 | self.textFiled.placeholder = viewModel.placeholder; 51 | self.textFiled.text = nil; 52 | if ([viewModel.title isEqualToString:@"appiumVersion"] || [viewModel.title isEqualToString:@"platformName"] || [viewModel.title isEqualToString:@"automationName"] || [viewModel.title isEqualToString:@"appiumLanguage"]) { 53 | [self.textFiled setEnabled:NO]; 54 | } else { 55 | [self.textFiled setEnabled:YES]; 56 | } 57 | 58 | if ([viewModel.title isEqualToString:@"udid"]){ 59 | self.rightBarButton.hidden = NO; 60 | } else { 61 | self.rightBarButton.hidden = YES; 62 | } 63 | } 64 | 65 | - (void)rightBarButtonClicked { 66 | NSURL *udidUrl = [NSURL URLWithString:kMobileConfigUrl]; 67 | [[UIApplication sharedApplication] openURL:udidUrl]; 68 | } 69 | 70 | #pragma mark--UITextFiledDelegate-- 71 | - (void)textFieldDidEndEditing:(UITextField *)textField { 72 | if (!textField.text) { 73 | return; 74 | } 75 | /*修改了cap之后,要更新本地cap缓存,并重新生成脚本*/ 76 | self.kvModel.placeholder = textField.text; 77 | [self.kvModel updateLocalCap]; 78 | /*刷新脚本section*/ 79 | if (self.delegate && [self.delegate respondsToSelector:@selector(capMapHasChanged)]) { 80 | [self.delegate capMapHasChanged]; 81 | } 82 | } 83 | 84 | #pragma mark--Getters & Setters-- 85 | - (UILabel *)titleLabel { 86 | if (!_titleLabel) { 87 | _titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 2, ([[UIScreen mainScreen] bounds].size.width)*0.33 - 12, 40)]; 88 | _titleLabel.font = [UIFont systemFontOfSize:15]; 89 | _titleLabel.textColor = [UIColor blackColor]; 90 | } 91 | return _titleLabel; 92 | } 93 | - (UITextField *)textFiled { 94 | if (!_textFiled) { 95 | _textFiled = [[UITextField alloc]initWithFrame:CGRectMake(([[UIScreen mainScreen] bounds].size.width)*0.33 + 12, 2, ([[UIScreen mainScreen] bounds].size.width)*0.66 - 12, 40)]; 96 | _textFiled.font = [UIFont systemFontOfSize:15]; 97 | _textFiled.delegate = self; 98 | } 99 | return _textFiled; 100 | } 101 | - (IQUIRightBarButton *)rightBarButton { 102 | if (!_rightBarButton){ 103 | _rightBarButton = [IQUIRightBarButton buttonWithType:UIButtonTypeDetailDisclosure]; 104 | _rightBarButton.hidden = YES; 105 | _rightBarButton.frame = CGRectMake([[UIScreen mainScreen] bounds].size.width - 60, 2, 60, 40); 106 | [_rightBarButton addTarget:self action:@selector(rightBarButtonClicked) forControlEvents:UIControlEventTouchUpInside]; 107 | } 108 | return _rightBarButton; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugKeyValueModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugKeyValueModel.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IQUITestDebugKVModel; 12 | 13 | @interface IQUITestDebugKeyValueModel : NSObject 14 | 15 | @property (nonatomic, copy, readonly) NSArray *kvArray; 16 | 17 | + (IQUITestDebugKeyValueModel *)viewModelWithCap:(id)cap; 18 | - (void)updateKVModelArray; 19 | 20 | @end 21 | 22 | @interface IQUITestDebugKVModel : NSObject 23 | 24 | @property (nonatomic, copy, readonly) NSString *title; 25 | @property (nonatomic, copy) NSString *placeholder; 26 | - (void)updateLocalCap; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugKeyValueModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugKeyValueModel.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugKeyValueModel.h" 10 | #import "IQUITestCodeMakerGenerator.h" 11 | 12 | @class IQAppiumCapabilities; 13 | 14 | static NSString *const kCapabilitiesKey = @"kCapabilitiesKey"; 15 | 16 | @interface IQUITestDebugKeyValueModel () 17 | 18 | @property (nonatomic, copy, readwrite) NSArray *kvArray; 19 | 20 | @end 21 | 22 | @interface IQUITestDebugKVModel () 23 | 24 | @property (nonatomic, copy, readwrite) NSString *title; 25 | 26 | @end 27 | 28 | @implementation IQUITestDebugKeyValueModel 29 | 30 | + (IQUITestDebugKeyValueModel *)viewModelWithCap:(id)cap { 31 | 32 | NSMutableArray *kvArray = [NSMutableArray array]; 33 | NSDictionary *localCap = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 34 | for (NSString *key in localCap.allKeys) { 35 | IQUITestDebugKVModel *model = [[IQUITestDebugKVModel 36 | alloc]init]; 37 | model.title = key; 38 | model.placeholder = localCap[key]; 39 | [kvArray addObject:model]; 40 | 41 | } 42 | 43 | IQUITestDebugKeyValueModel *model = [[IQUITestDebugKeyValueModel alloc]init]; 44 | model.kvArray = [kvArray copy]; 45 | return model; 46 | } 47 | 48 | - (void)updateKVModelArray { 49 | NSMutableArray *kvArray = [NSMutableArray array]; 50 | NSDictionary *localCap = [[NSUserDefaults standardUserDefaults] objectForKey:kCapabilitiesKey]; 51 | for (NSString *key in localCap.allKeys) { 52 | IQUITestDebugKVModel *model = [[IQUITestDebugKVModel 53 | alloc]init]; 54 | model.title = key; 55 | model.placeholder = localCap[key]; 56 | [kvArray addObject:model]; 57 | } 58 | self.kvArray = [kvArray copy]; 59 | } 60 | 61 | @end 62 | 63 | @implementation IQUITestDebugKVModel 64 | 65 | - (void)updateLocalCap { 66 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 67 | [persistent handleCapChangeTaskWithKey:self.title value:self.placeholder]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugScriptCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugScriptCell.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IQUITestDebugScriptModel; 12 | 13 | @protocol IQUITestDebugScriptDelegate 14 | 15 | - (void)scriptCodeHasGenerated; 16 | 17 | @end 18 | 19 | @interface IQUITestDebugScriptCell : UITableViewCell 20 | 21 | @property (nonatomic, weak) iddelegate; 22 | 23 | - (void)updateViewWithViewModel:(IQUITestDebugScriptModel *)model; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugScriptCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugScriptCell.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugScriptCell.h" 10 | #import "IQUITestDebugScriptModel.h" 11 | #import "IQUITestCodeMakerGenerator.h" 12 | #import "IQUITestCodeMakerFactory.h" 13 | #import "IQUITestCodeMakerCapabilities.h" 14 | 15 | @interface IQUITestDebugSegmentControl : UISegmentedControl 16 | 17 | @end 18 | 19 | @implementation IQUITestDebugSegmentControl 20 | 21 | @end 22 | 23 | @interface IQUITestDebugScriptCell () 24 | 25 | @property (nonatomic, strong) IQUITestDebugSegmentControl *segmentControl; 26 | @property (nonatomic, strong) UITextView *codeText; 27 | @property (nonatomic, strong) NSArray *itemsArray; 28 | @property (nonatomic, strong) IQUITestDebugScriptModel *sciptModel; 29 | 30 | @end 31 | 32 | @implementation IQUITestDebugScriptCell 33 | 34 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 35 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 36 | [self setUpSubviews]; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)awakeFromNib { 42 | [super awakeFromNib]; 43 | // Initialization code 44 | } 45 | 46 | - (void)setUpSubviews { 47 | self.backgroundColor = [UIColor clearColor]; 48 | [self.contentView addSubview:self.segmentControl]; 49 | [self.contentView addSubview:self.codeText]; 50 | } 51 | 52 | - (void)updateViewWithViewModel:(IQUITestDebugScriptModel *)model { 53 | self.sciptModel = model; 54 | 55 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 56 | NSInteger selectIndex = [self.itemsArray indexOfObject:persistent.factory.cap.appiumCap.appiumLanguage]; 57 | self.segmentControl.selectedSegmentIndex = selectIndex; 58 | 59 | NSString *script = [NSString stringWithContentsOfFile:persistent.factory.scriptPath encoding:NSUTF8StringEncoding error:NULL]; 60 | self.codeText.text = script; 61 | } 62 | 63 | - (void)selectItem:(IQUITestDebugSegmentControl *)segmentControl { 64 | /* 65 | 1.保存当前的event queue。 66 | 2.重新实力化cap。 67 | 3.重新实力化脚本工厂。 68 | 4.进行脚本转换。 69 | */ 70 | [self.sciptModel handleSegmentControlSelected:segmentControl.selectedSegmentIndex withCallBack:^{ 71 | [self reloadCodeText]; 72 | /*刷新cap的section*/ 73 | if (self.delegate && [self.delegate respondsToSelector:@selector(scriptCodeHasGenerated)]) { 74 | [self.delegate scriptCodeHasGenerated]; 75 | } 76 | }]; 77 | } 78 | 79 | - (void)reloadCodeText { 80 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 81 | NSString *script = [NSString stringWithContentsOfFile:persistent.factory.scriptPath encoding:NSUTF8StringEncoding error:NULL]; 82 | self.codeText.text = script; 83 | } 84 | 85 | #pragma mark--Getters & Setters-- 86 | - (IQUITestDebugSegmentControl *)segmentControl { 87 | if (!_segmentControl) { 88 | _segmentControl = [[IQUITestDebugSegmentControl alloc]initWithItems:self.itemsArray]; 89 | _segmentControl.frame = CGRectMake(12, 5, [[UIScreen mainScreen] bounds].size.width - 24, 30); 90 | _segmentControl.selectedSegmentIndex = 0;/*取用户选择的语言*/ 91 | [_segmentControl addTarget:self action:@selector(selectItem:) forControlEvents:UIControlEventValueChanged]; 92 | } 93 | return _segmentControl; 94 | } 95 | 96 | - (UITextView *)codeText { 97 | if (!_codeText) { 98 | _codeText = [[UITextView alloc]initWithFrame:CGRectMake(12, 40, [[UIScreen mainScreen] bounds].size.width - 24, 300)]; 99 | _codeText.backgroundColor = [UIColor clearColor]; 100 | _codeText.font = [UIFont systemFontOfSize:13]; 101 | _codeText.layer.cornerRadius = 5; 102 | _codeText.clipsToBounds = YES; 103 | _codeText.editable = NO; 104 | } 105 | return _codeText; 106 | } 107 | 108 | - (NSArray *)itemsArray { 109 | if (!_itemsArray) { 110 | _itemsArray = @[@"Ruby",@"Python",@"JSWdio",@"JSPromise",@"Java"]; 111 | } 112 | return _itemsArray; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugScriptModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugScriptModel.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^IQHandleScriptBlock)(void); 12 | 13 | @interface IQUITestDebugScriptModel : NSObject 14 | 15 | @property (nonatomic, copy, readonly) NSArray *codeTextArray; 16 | 17 | + (IQUITestDebugScriptModel *)viewModelWithScript:(NSString *)scriptPath; 18 | 19 | - (void)handleSegmentControlSelected:(NSInteger)selectIndex withCallBack:(IQHandleScriptBlock)cabllBack; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugScriptModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugScriptModel.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugScriptModel.h" 10 | #import "IQUITestCodeMakerGenerator.h" 11 | #import "IQUITestCodeMakerCapabilities.h" 12 | #import "IQUITestCodeMakerFactory.h" 13 | 14 | @interface IQUITestDebugScriptModel () 15 | 16 | @property (nonatomic, copy, readwrite) NSArray *codeTextArray; 17 | 18 | @end 19 | 20 | @implementation IQUITestDebugScriptModel 21 | 22 | + (IQUITestDebugScriptModel *)viewModelWithScript:(NSString *)scriptPath { 23 | IQUITestDebugScriptModel *model = [[IQUITestDebugScriptModel alloc]init]; 24 | model.codeTextArray = @[@"asadadadadsadadadadasdadasdadadas",@"asadadadadsadadadadasdadasdadadas",@"asadadadadsadadadadasdadasdadadas"]; 25 | return model; 26 | } 27 | 28 | - (void)handleSegmentControlSelected:(NSInteger)selectIndex withCallBack:(IQHandleScriptBlock)cabllBack { 29 | /*update local cap cache*/ 30 | NSArray * items = @[@"Ruby",@"Python",@"JSWdio",@"JSPromise",@"Java"]; 31 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 32 | [persistent handleConvertTaskWithIdentifier:items[selectIndex]]; 33 | 34 | if (cabllBack) { 35 | cabllBack(); 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugServerInfoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugServerInfoCell.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/7. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IQUITestDebugServerInfoModel; 12 | @interface IQUITestDebugServerInfoCell : UITableViewCell 13 | 14 | - (void)updateViewWithViewModel:(IQUITestDebugServerInfoModel *)model; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugServerInfoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugServerInfoCell.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/7. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugServerInfoCell.h" 10 | #import "IQUITestDebugServerInfoModel.h" 11 | 12 | @interface IQUITestDebugServerInfoCell () 13 | 14 | @property (nonatomic, strong) UILabel *titleLabel; 15 | 16 | @end 17 | 18 | @implementation IQUITestDebugServerInfoCell 19 | 20 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 21 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 22 | [self setUpSubviews]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)setUpSubviews { 28 | self.backgroundColor = [UIColor clearColor]; 29 | [self.contentView addSubview:self.titleLabel]; 30 | } 31 | 32 | - (void)updateViewWithViewModel:(IQUITestDebugServerInfoModel *)model { 33 | self.titleLabel.text = model.serverUrl; 34 | } 35 | 36 | #pragma mark--Getters & Setters-- 37 | - (UILabel *)titleLabel { 38 | if (!_titleLabel) { 39 | _titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 2, ([[UIScreen mainScreen] bounds].size.width) - 24, 40)]; 40 | _titleLabel.font = [UIFont systemFontOfSize:15]; 41 | _titleLabel.textColor = [UIColor blackColor]; 42 | } 43 | return _titleLabel; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugServerInfoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugServerInfoModel.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface IQUITestDebugServerInfoModel : NSObject 12 | 13 | @property (nonatomic, copy, readonly) NSString *serverUrl; 14 | 15 | + (IQUITestDebugServerInfoModel *)viewModelWithServer:(id)server; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugServerInfoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugServerInfoModel.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugServerInfoModel.h" 10 | #import "IQUITestCodeMakerGenerator.h" 11 | #import "GCDWebServer.h" 12 | 13 | @interface IQUITestDebugServerInfoModel () 14 | 15 | @property (nonatomic, copy, readwrite) NSString *serverUrl; 16 | 17 | @end 18 | 19 | @implementation IQUITestDebugServerInfoModel 20 | 21 | + (IQUITestDebugServerInfoModel *)viewModelWithServer:(id)server { 22 | IQUITestDebugServerInfoModel *infoModel = [[IQUITestDebugServerInfoModel alloc]init]; 23 | infoModel.serverUrl = [IQUITestCodeMakerGenerator sharePersistent].webServer.serverURL.absoluteString; 24 | return infoModel; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugSwitchCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugSwitchCell.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class IQUITestDebugSwitchModel; 12 | 13 | @protocol IQUITestDebugSwitchDelegate 14 | 15 | - (void)reordControlStateHasChanged; 16 | 17 | @end 18 | 19 | @interface IQUITestDebugSwitchCell : UITableViewCell 20 | 21 | @property (nonatomic, weak) iddelegate; 22 | 23 | - (void)updateViewWithViewModel:(IQUITestDebugSwitchModel *)model; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugSwitchCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugSwitchCell.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugSwitchCell.h" 10 | #import "IQUITestDebugSwitchModel.h" 11 | 12 | @interface IQUITestDebugSwitch : UISwitch 13 | 14 | @end 15 | 16 | @implementation IQUITestDebugSwitch 17 | 18 | @end 19 | 20 | @interface IQUITestDebugSwitchCell () 21 | 22 | @property (nonatomic, strong) UILabel *titleLabel; 23 | @property (nonatomic, strong) IQUITestDebugSwitch *stopRecord; 24 | @property (nonatomic, strong) IQUITestDebugSwitchModel *swModel; 25 | 26 | @end 27 | 28 | @implementation IQUITestDebugSwitchCell 29 | 30 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 31 | if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { 32 | [self setUpSubviews]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)setUpSubviews { 38 | self.backgroundColor = [UIColor clearColor]; 39 | [self.contentView addSubview:self.titleLabel]; 40 | [self.contentView addSubview:self.stopRecord]; 41 | } 42 | 43 | - (void)stopSwitchClick:(UISwitch*)sw { 44 | [self.swModel handleSwitchState:sw.isOn withCallBack:^{ 45 | /*刷新脚本section*/ 46 | if (self.delegate && [self.delegate respondsToSelector:@selector(reordControlStateHasChanged)]) { 47 | [self.delegate reordControlStateHasChanged]; 48 | } 49 | }]; 50 | } 51 | 52 | - (void)updateViewWithViewModel:(IQUITestDebugSwitchModel *)model { 53 | self.swModel = model; 54 | self.titleLabel.text = model.title; 55 | self.stopRecord.on = model.isSwitchOn; 56 | } 57 | 58 | #pragma mark--Getters & Setters-- 59 | - (UILabel *)titleLabel { 60 | if (!_titleLabel) { 61 | _titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(12, 2, ([[UIScreen mainScreen] bounds].size.width) - 80, 40)]; 62 | _titleLabel.font = [UIFont systemFontOfSize:15]; 63 | _titleLabel.textColor = [UIColor blackColor]; 64 | } 65 | return _titleLabel; 66 | } 67 | 68 | - (IQUITestDebugSwitch *)stopRecord { 69 | if (!_stopRecord) { 70 | _stopRecord = [[IQUITestDebugSwitch alloc]initWithFrame:CGRectMake([[UIScreen mainScreen] bounds].size.width - 12 - 50, 5, 50, 40)]; 71 | [_stopRecord addTarget:self action:@selector(stopSwitchClick:) forControlEvents:UIControlEventTouchUpInside]; 72 | _stopRecord.on = NO; 73 | } 74 | return _stopRecord; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugSwitchModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugSwitchModel.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^IQHandleSwitchBlock)(void); 12 | 13 | @interface IQUITestDebugSwitchModel : NSObject 14 | 15 | @property (nonatomic, copy, readonly) NSString *title; 16 | @property (nonatomic, assign,getter=isSwitchOn) BOOL swOn; 17 | 18 | + (IQUITestDebugSwitchModel *)viewModelWithState:(BOOL)state; 19 | - (void)handleSwitchState:(BOOL)state withCallBack:(IQHandleSwitchBlock)callBack; 20 | - (void)updateSwitchModel; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugSwitchModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugSwitchModel.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugSwitchModel.h" 10 | #import "IQUITestCodeMakerGenerator.h" 11 | #import "IQUITestOperationEvent.h" 12 | #import "IQUITestCodeMakerFactory.h" 13 | 14 | @interface IQUITestDebugSwitchModel () 15 | 16 | @property (nonatomic, copy, readwrite) NSString *title; 17 | 18 | @end 19 | 20 | @implementation IQUITestDebugSwitchModel 21 | 22 | + (IQUITestDebugSwitchModel *)viewModelWithState:(BOOL)state { 23 | IQUITestDebugSwitchModel *model = [[IQUITestDebugSwitchModel alloc]init]; 24 | model.title = @"重新开启录制会清除上次缓存脚本"; 25 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 26 | IQUITestOperationEvent *op = persistent.factory.eventQueue.lastObject; 27 | if (op && (op.eventType != IQEventEndCode)) { 28 | model.swOn = YES; 29 | } else { 30 | model.swOn = NO; 31 | } 32 | return model; 33 | } 34 | 35 | - (void)updateSwitchModel { 36 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 37 | IQUITestOperationEvent *op = persistent.factory.eventQueue.lastObject; 38 | if (op && (op.eventType != IQEventEndCode)) { 39 | self.swOn = YES; 40 | } else { 41 | self.swOn = NO; 42 | } 43 | } 44 | 45 | - (void)handleSwitchState:(BOOL)state withCallBack:(IQHandleSwitchBlock)callBack { 46 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 47 | [persistent handleRecordControlEventWithState:state]; 48 | 49 | if (callBack) { 50 | callBack(); 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugTable.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugTable.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^IQDebugBlock)(void); 12 | 13 | @interface IQUITestDebugTable : UIView 14 | 15 | @property (nonatomic, copy)IQDebugBlock debugBlock; 16 | 17 | - (void)showDebugView; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestDebugTable.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestDebugTable.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/6. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestDebugTable.h" 10 | #import "IQUITestDebugKeyValueModel.h" 11 | #import "IQUITestDebugScriptModel.h" 12 | #import "IQUITestDebugSwitchModel.h" 13 | #import "IQUITestDebugServerInfoModel.h" 14 | #import "IQUITestDebugKeyValueCell.h" 15 | #import "IQUITestDebugSwitchCell.h" 16 | #import "IQUITestDebugScriptCell.h" 17 | #import "IQUITestCodeMakerGenerator.h" 18 | #import "IQUITestDebugServerInfoCell.h" 19 | 20 | @interface IQUITestDebugDismissButton : UIButton 21 | 22 | @end 23 | 24 | @implementation IQUITestDebugDismissButton 25 | 26 | 27 | @end 28 | 29 | @interface IQUITestDebugTable () 30 | 31 | @property (nonatomic, strong) IQUITestDebugDismissButton *dismissButton; 32 | @property (nonatomic, strong) UITableView *debugTable; 33 | @property (nonatomic, strong) UIImageView *blurImgv; 34 | @property (nonatomic, strong) UIVisualEffectView *effectView; 35 | @property (nonatomic, strong) NSMutableArray *dataArray; 36 | 37 | @end 38 | 39 | @implementation IQUITestDebugTable 40 | 41 | - (instancetype)initWithFrame:(CGRect)frame { 42 | if (self = [super initWithFrame:frame]) { 43 | [self setUpSubviews]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)setUpSubviews { 49 | self.blurImgv = [[UIImageView alloc]initWithFrame:self.bounds]; 50 | self.blurImgv.image = [self imageWithScreenshot]; 51 | [self addSubview:self.blurImgv]; 52 | 53 | UIBlurEffect *beffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 54 | self.effectView = [[UIVisualEffectView alloc]initWithEffect:beffect]; 55 | self.effectView.frame = self.blurImgv.frame; 56 | [self addSubview:self.effectView]; 57 | 58 | self.debugTable.tableHeaderView = self.dismissButton; 59 | [self addSubview:self.debugTable]; 60 | [self.debugTable reloadData]; 61 | } 62 | 63 | - (void)dismissDebugView { 64 | if (self.debugBlock) { 65 | self.debugBlock(); 66 | } 67 | self.hidden = YES; 68 | } 69 | 70 | - (void)showDebugView { 71 | self.hidden = NO; 72 | [self.blurImgv removeFromSuperview]; 73 | [self.effectView removeFromSuperview]; 74 | 75 | self.blurImgv = [[UIImageView alloc]initWithFrame:self.bounds]; 76 | self.blurImgv.image = [self imageWithScreenshot]; 77 | [self insertSubview:self.blurImgv atIndex:0]; 78 | 79 | UIBlurEffect *beffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; 80 | self.effectView = [[UIVisualEffectView alloc]initWithEffect:beffect]; 81 | self.effectView.frame = self.blurImgv.frame; 82 | [self insertSubview:self.effectView atIndex:1]; 83 | [self.debugTable reloadData]; 84 | } 85 | 86 | - (UIImage *)imageWithScreenshot 87 | { 88 | CGSize imageSize = CGSizeZero; 89 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 90 | if (UIInterfaceOrientationIsPortrait(orientation)) 91 | imageSize = [UIScreen mainScreen].bounds.size; 92 | else 93 | imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width); 94 | 95 | UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0); 96 | CGContextRef context = UIGraphicsGetCurrentContext(); 97 | for (UIWindow *window in [[UIApplication sharedApplication] windows]) 98 | { 99 | CGContextSaveGState(context); 100 | CGContextTranslateCTM(context, window.center.x, window.center.y); 101 | CGContextConcatCTM(context, window.transform); 102 | CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y); 103 | if (orientation == UIInterfaceOrientationLandscapeLeft) 104 | { 105 | CGContextRotateCTM(context, M_PI_2); 106 | CGContextTranslateCTM(context, 0, -imageSize.width); 107 | } 108 | else if (orientation == UIInterfaceOrientationLandscapeRight) 109 | { 110 | CGContextRotateCTM(context, -M_PI_2); 111 | CGContextTranslateCTM(context, -imageSize.height, 0); 112 | } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { 113 | CGContextRotateCTM(context, M_PI); 114 | CGContextTranslateCTM(context, -imageSize.width, -imageSize.height); 115 | } 116 | if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]){ 117 | [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; 118 | }else{ 119 | [window.layer renderInContext:context]; 120 | } 121 | CGContextRestoreGState(context); 122 | } 123 | 124 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 125 | UIGraphicsEndImageContext(); 126 | NSData *data = UIImagePNGRepresentation(image); 127 | UIImage *returnImage = [UIImage imageWithData:data]; 128 | return returnImage; 129 | } 130 | 131 | #pragma mark--UITableViewDelegate-- 132 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 133 | return self.dataArray.count; 134 | } 135 | 136 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 137 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 138 | } 139 | 140 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 141 | id sectionModel = self.dataArray[section]; 142 | if ([sectionModel isKindOfClass:[IQUITestDebugKeyValueModel class]]) { 143 | return @"设置Desired Capabilities"; 144 | } else if ([sectionModel isKindOfClass:[IQUITestDebugServerInfoModel class]]) { 145 | return @"打开下面链接查看或导出脚本"; 146 | } else if ([sectionModel isKindOfClass:[IQUITestDebugSwitchModel class]]) { 147 | return @"录制控制"; 148 | } else if ([sectionModel isKindOfClass:[IQUITestDebugScriptModel class]]) { 149 | return @"脚本转换"; 150 | } 151 | return @""; 152 | } 153 | 154 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 155 | id sectionModel = self.dataArray[section]; 156 | if ([sectionModel isKindOfClass:[IQUITestDebugKeyValueModel class]]) { 157 | IQUITestDebugKeyValueModel *kvModel = (IQUITestDebugKeyValueModel *)sectionModel; 158 | return kvModel.kvArray.count; 159 | } else if ([sectionModel isKindOfClass:[IQUITestDebugServerInfoModel class]]) { 160 | return 1; 161 | } else if ([sectionModel isKindOfClass:[IQUITestDebugSwitchModel class]]) { 162 | return 1; 163 | } else if ([sectionModel isKindOfClass:[IQUITestDebugScriptModel class]]) { 164 | return 1; 165 | } 166 | return 0; 167 | } 168 | #pragma mark--UITableViewDataSource-- 169 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 170 | id sectionModel = self.dataArray[indexPath.section]; 171 | if ([sectionModel isKindOfClass:[IQUITestDebugKeyValueModel class]]) { 172 | return 44; 173 | } else if ([sectionModel isKindOfClass:[IQUITestDebugServerInfoModel class]]) { 174 | return 44; 175 | } else if ([sectionModel isKindOfClass:[IQUITestDebugSwitchModel class]]) { 176 | return 44; 177 | } else if ([sectionModel isKindOfClass:[IQUITestDebugScriptModel class]]) { 178 | return 340; 179 | } 180 | return 0; 181 | } 182 | 183 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 184 | 185 | id sectionModel = self.dataArray[indexPath.section]; 186 | if ([sectionModel isKindOfClass:[IQUITestDebugKeyValueModel class]]) { 187 | static NSString *reuseId = @"IQUITestDebugKeyValueCell"; 188 | IQUITestDebugKeyValueModel *kvModel = (IQUITestDebugKeyValueModel *)sectionModel; 189 | IQUITestDebugKeyValueCell *keyValueCell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 190 | if (!keyValueCell) { 191 | keyValueCell = [[IQUITestDebugKeyValueCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId]; 192 | keyValueCell.delegate = self; 193 | } 194 | [keyValueCell updateViewWithViewModel:kvModel.kvArray[indexPath.row]]; 195 | return keyValueCell; 196 | 197 | } else if ([sectionModel isKindOfClass:[IQUITestDebugServerInfoModel class]]) { 198 | static NSString *reuseId = @"IQUITestDebugServerInfoCell"; 199 | IQUITestDebugServerInfoModel *infoModel = (IQUITestDebugServerInfoModel *)sectionModel; 200 | 201 | IQUITestDebugServerInfoCell *serverInfoCell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 202 | if (!serverInfoCell) { 203 | serverInfoCell = [[IQUITestDebugServerInfoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId]; 204 | } 205 | [serverInfoCell updateViewWithViewModel:infoModel]; 206 | return serverInfoCell; 207 | } else if ([sectionModel isKindOfClass:[IQUITestDebugSwitchModel class]]) { 208 | static NSString *reuseId = @"IQUITestDebugSwitchCell"; 209 | IQUITestDebugSwitchModel *swModel = (IQUITestDebugSwitchModel *)sectionModel; 210 | IQUITestDebugSwitchCell *switchCell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 211 | if (!switchCell) { 212 | switchCell = [[IQUITestDebugSwitchCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId]; 213 | switchCell.delegate = self; 214 | } 215 | [swModel updateSwitchModel]; 216 | [switchCell updateViewWithViewModel:swModel]; 217 | return switchCell; 218 | } else if ([sectionModel isKindOfClass:[IQUITestDebugScriptModel class]]) { 219 | static NSString *reuseId = @"IQUITestDebugSwitchCell"; 220 | IQUITestDebugScriptModel *scriptModel = (IQUITestDebugScriptModel *)sectionModel; 221 | IQUITestDebugScriptCell *scriptCell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 222 | if (!scriptCell) { 223 | scriptCell = [[IQUITestDebugScriptCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId]; 224 | scriptCell.delegate = self; 225 | } 226 | [scriptCell updateViewWithViewModel:scriptModel]; 227 | return scriptCell; 228 | } 229 | return nil; 230 | 231 | } 232 | 233 | #pragma mark--IQUITestDebugKeyValueDelegate-- 234 | - (void)capMapHasChanged { 235 | IQUITestDebugKeyValueModel *kvModel = self.dataArray[0]; 236 | [kvModel updateKVModelArray]; 237 | [self.debugTable reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 238 | [self.debugTable reloadSections:[NSIndexSet indexSetWithIndex:3] withRowAnimation:UITableViewRowAnimationFade]; 239 | } 240 | #pragma mark--IQUITestDebugSwitchDelegate-- 241 | - (void)reordControlStateHasChanged { 242 | /*刷新script section*/ 243 | [self.debugTable reloadSections:[NSIndexSet indexSetWithIndex:3] withRowAnimation:UITableViewRowAnimationFade]; 244 | } 245 | #pragma mark--IQUITestDebugScriptDelegate-- 246 | - (void)scriptCodeHasGenerated { 247 | IQUITestDebugKeyValueModel *kvModel = self.dataArray[0]; 248 | [kvModel updateKVModelArray]; 249 | [self.debugTable reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 250 | } 251 | #pragma mark--Getters & Setters-- 252 | - (IQUITestDebugDismissButton *)dismissButton { 253 | if (!_dismissButton) { 254 | _dismissButton = [IQUITestDebugDismissButton buttonWithType:UIButtonTypeCustom]; 255 | _dismissButton.frame = CGRectMake(12, 60, [[UIScreen mainScreen] bounds].size.width - 24, 60); 256 | [_dismissButton addTarget:self action:@selector(dismissDebugView) forControlEvents:UIControlEventTouchUpInside]; 257 | [_dismissButton setTitle:@"🤪 Tap me to dismiss the Debug View 🤪" forState:UIControlStateNormal]; 258 | [_dismissButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 259 | _dismissButton.titleLabel.font = [UIFont fontWithName:@"MarkerFelt-Thin" size:20]; 260 | _dismissButton.titleLabel.adjustsFontSizeToFitWidth = YES; 261 | } 262 | return _dismissButton; 263 | } 264 | - (UITableView *)debugTable { 265 | if (!_debugTable) { 266 | _debugTable = [[UITableView alloc]initWithFrame:CGRectMake(0, 60, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height - 60) style:UITableViewStyleGrouped]; 267 | _debugTable.backgroundColor = [UIColor clearColor]; 268 | _debugTable.delegate = self; 269 | _debugTable.dataSource = self; 270 | } 271 | return _debugTable; 272 | } 273 | 274 | - (NSMutableArray *)dataArray { 275 | if (!_dataArray) { 276 | _dataArray = [NSMutableArray array]; 277 | IQUITestDebugKeyValueModel *kvModel = [IQUITestDebugKeyValueModel viewModelWithCap:nil]; 278 | IQUITestDebugServerInfoModel *infoModel = [IQUITestDebugServerInfoModel viewModelWithServer:nil]; 279 | IQUITestDebugSwitchModel *swModel = [IQUITestDebugSwitchModel viewModelWithState:YES]; 280 | IQUITestDebugScriptModel *scriptModel = [IQUITestDebugScriptModel viewModelWithScript:nil]; 281 | 282 | [_dataArray addObject:kvModel]; 283 | [_dataArray addObject:infoModel]; 284 | [_dataArray addObject:swModel]; 285 | [_dataArray addObject:scriptModel]; 286 | } 287 | return _dataArray; 288 | } 289 | 290 | @end 291 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestOperationEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestOperationEvent.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSInteger,IQEventType ) { 13 | IQUIEventUnknown = 0,/*未知*/ 14 | IQUIEventTap = 1,/*单击*/ 15 | IQUIEventDoubleTap = 2,/*双击*/ 16 | IQUIEventSwipe = 3,/*轻扫*/ 17 | IQUIEventPinch = 4,/*捏合*/ 18 | IQUIEventZoom = 5,/*放大*/ 19 | IQUIEventLongPress = 6,/*长按*/ 20 | IQUIEventSendKey = 7,/*输入文本*/ 21 | IQEventTemplateCode = 8,/*开始录制事件*/ 22 | IQEventEndCode = 9,/*结束录制事件*/ 23 | }; 24 | 25 | typedef NS_ENUM(NSInteger,IQElementLocateStrategy ) { 26 | IQElementLocateByIdentifier = 0,/*通过控件identifier定位元素*/ 27 | IQElementLocateByCoordinate = 1,/*通过坐标定位元素*/ 28 | IQElementLocateByPredicate = 2,/*通过谓词定位元素*/ 29 | }; 30 | 31 | @interface IQUITestOperationEvent : NSObject 32 | 33 | @property (nonatomic, assign) IQElementLocateStrategy locateStrategy;/*定位策略,默认IQElementLocateByIdentifier*/ 34 | @property (nonatomic, assign) IQEventType eventType; 35 | @property (nonatomic, copy) NSString *identifier; 36 | @property (nonatomic, copy) NSString *value; 37 | @property (nonatomic, assign) CGPoint touchPoint; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestOperationEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestOperationEvent.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "IQUITestOperationEvent.h" 10 | 11 | @implementation IQUITestOperationEvent 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/IQUITestProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQUITestProtocol.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol IQUITestProtocol 12 | 13 | - (void)handleApplicationDidFinishLaunching; 14 | - (void)handleApplicationWillResignActiveNotification; 15 | - (void)handleApplicationWillTerminateNotification; 16 | - (void)handleApplicationDidReceiveMemoryWarningNotification; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/UIApplication+IQGlobalHook.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+IQGlobalHook.h 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIApplication (IQGlobalHook) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /IQUITestCodeMaker/UIApplication+IQGlobalHook.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+IQGlobalHook.m 3 | // IQUITestCodeMaker 4 | // 5 | // Created by lobster on 2018/8/4. 6 | // Copyright © 2018年 lobster. All rights reserved. 7 | // 8 | 9 | #import "UIApplication+IQGlobalHook.h" 10 | #import "IQUITestCodeMakerGenerator.h" 11 | #import "IQUITestCodeMaker.h" 12 | #import "IQUITestProtocol.h" 13 | #import "GCDWebServer.h" 14 | 15 | @implementation UIApplication (IQGlobalHook) 16 | 17 | + (void)load { 18 | #if IQ_CODEMAKER_ENABLED 19 | 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | IQUITestCodeMakerGenerator *persistent = [IQUITestCodeMakerGenerator sharePersistent]; 23 | [persistent hook]; 24 | /*start web server*/ 25 | if ([persistent.webServer start]) { 26 | NSLog(@"%@",[NSString stringWithFormat:NSLocalizedString(@"GCDWebServer running locally on port %i", nil), (int)persistent.webServer.port]); 27 | } else { 28 | NSLog(@"GCDWebServer not running!"); 29 | } 30 | 31 | [[NSNotificationCenter defaultCenter] addObserver:persistent selector:@selector(handleApplicationDidFinishLaunching) name:UIApplicationDidFinishLaunchingNotification object:nil]; 32 | 33 | }); 34 | 35 | #endif 36 | } 37 | 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | source 'https://github.com/CocoaPods/Specs.git' 4 | 5 | platform :ios, '8.0' 6 | target 'IQUITestCodeMaker' do 7 | 8 | pod 'GCDWebServer' 9 | pod 'Aspects' 10 | 11 | end 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Aspects (1.4.1) 3 | - GCDWebServer (3.4.2): 4 | - GCDWebServer/Core (= 3.4.2) 5 | - GCDWebServer/Core (3.4.2) 6 | 7 | DEPENDENCIES: 8 | - Aspects 9 | - GCDWebServer 10 | 11 | SPEC REPOS: 12 | https://github.com/cocoapods/specs.git: 13 | - Aspects 14 | - GCDWebServer 15 | 16 | SPEC CHECKSUMS: 17 | Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 18 | GCDWebServer: 8d67ee9f634b4bb91eb4b8aee440318a5fc6debd 19 | 20 | PODFILE CHECKSUM: a13335341420340ae45105db52266ac155b6882a 21 | 22 | COCOAPODS: 1.6.1 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | [![Build Status](https://travis-ci.org/Lobster-King/IQUITestCodeMaker.svg?branch=master)](https://travis-ci.org/Lobster-King/IQUITestCodeMaker) 4 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Lobster-King/IQUITestCodeMaker/blob/master/LICENSE) 5 | [![CocoaPods](https://img.shields.io/cocoapods/v/IQUITestCodeMaker.svg)](http://cocoapods.org/pods/IQUITestCodeMaker) 6 | [![Github All Releases](https://img.shields.io/github/downloads/Lobster-King/IQUITestCodeMaker/total.svg)](https://github.com/Lobster-King/IQUITestCodeMaker) 7 | [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/lobster_geek) 8 | 9 | [中文说明](https://github.com/Lobster-King/IQUITestCodeMaker/blob/master/README_CN.md) 10 | 11 | IQUITestCodeMaker is a modern and lightweight tool for generating iOS UI test script codes automatically without lanuching Appium Desktop or using inspector. 12 | 13 | ## A series of IQUITestCodeMaker concepts 14 | 15 | * Non intrusive that you don't need to do anything but downloading it using cocoapods. 16 | * Generating script codes automatically. 17 | * In order to make the result more accurate,you are advised to set accessibilityIdentifier manually. 18 | * For performance reasons,you are advised to turn off IQUITestCodeMaker in release mode. 19 | * With the debug ball support,you can export script file to desktop easily. 20 | 21 | ## Support Languages 22 | 23 | - [x] Ruby 24 | - [x] Python 25 | - [x] JavaScript(webdriverio) 26 | - [x] JavaScript(Promise) 27 | - [x] Java 28 | - [ ] Objective-C 29 | - [ ] Swift 30 | 31 | ## Reference 32 | * [Generating UI test label automatically for iOS](https://github.com/yulingtianxia/TBUIAutoTest) 33 | * [Appium Server and Inspector in Desktop GUIs for Mac, Windows, and Linux](https://github.com/appium/appium-desktop) 34 | 35 | # Get Started 36 | 37 | ## Installation 38 | 39 | ### cocoapods 40 | 41 | ```bash 42 | $ gem install cocoapods 43 | ``` 44 | 45 | ### edit podfile 46 | ``` 47 | source 'https://github.com/CocoaPods/Specs.git' 48 | platform :ios, '8.0' 49 | 50 | target 'Example' do 51 | pod 'IQUITestCodeMaker',:git => 'https://github.com/Lobster-King/IQUITestCodeMaker.git',:configurations => ['Debug'] 52 | end 53 | ``` 54 | ### pod install 55 | ```bash 56 | $ pod install 57 | ``` 58 | 59 | # Debug Ball 60 | 61 | ![Demo gif](https://github.com/Lobster-King/IQUITestCodeMaker/blob/master/Demo.gif) 62 | 63 | 64 | # About appium 65 | Appium is an open source, cross-platform test automation tool for native, 66 | hybrid and mobile web and desktop apps. It support simulators (iOS), emulators 67 | (Android), and real devices (iOS, Android, Windows, Mac). 68 | 69 | ## Documentation 70 | * [How to install appium ?](https://github.com/appium/appium/blob/master/docs/en/about-appium/getting-started.md) 71 | 72 | 73 | 74 | 75 | # Roadmap 76 | 77 | * more languages support. 78 | * macaca support. 79 | * ui element location strategy. 80 | 81 | # Have a problem? 82 | 83 | You can contact me in the following ways 84 | 85 | * PRs or Issues. 86 | * Email :[zhiwei.geek@gmail.com](mailto:zhiwei.geek@gmail.com) 87 | 88 | 89 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # 总览 2 | 3 | [英文说明](https://github.com/Lobster-King/IQUITestCodeMaker/blob/master/README.md) 4 | 5 | IQUITestCodeMaker是一款轻量级的、无侵入性的自动生成UI测试脚本的工具,目前支持Appium相关脚本代码的自动生成,后续会支持对阿里Macaca相关主流框架脚本代码的自动生成。 6 | 7 | Tester:对于测试朋友来讲,你需要做的就是让你们的iOSer把该框架通过pod引入工程即可,然后打出一个DEBUG包给到你。你不需要启动Appium Desktop或者启动inspector服务去编写UI测试脚本,你只需要用打出的DEBUG新包点点点,脚本会自动生成,然后利用Debug工具把脚本导出到桌面即可。 8 | 9 | iOSer:利用IQUITestCodeMaker中截获用户事件的相关实现,结合控件唯一标识方案,还可以做很多事情(比如无侵入打点等)。 10 | 11 | ## IQUITestCodeMaker特性 12 | 13 | * 无侵入性,不需要开发人员添加额外代码到工程中,你只需要用pod安装一下依赖即可。 14 | * 所有的流程都是自动化的,不需要人为介入,用户的操作事件会被自动截获并生成相关代码。 15 | * 为了让测试结果更准确,强烈建议开发人员手动给控件设置accessibilityIdentifier,通过runtime生成的id并不完全可靠,而且会有一定的性能开销。 16 | * IQUITestCodeMaker默认再DEBUG模式开启,在RELEASE模式关闭。 17 | * 通过Debug Ball,你可以很方便的把脚本导出到桌面,你也可以很方便的进行代码转换。 18 | 19 | ## 目前支持的语言 20 | - [x] Ruby 21 | - [x] Python 22 | - [x] JavaScript(webdriverio) 23 | - [x] JavaScript(Promise) 24 | - [x] Java 25 | - [ ] Objective-C 26 | - [ ] Swift 27 | 28 | ## 参考文档 29 | * [Generating UI test label automatically for iOS(腾讯大佬给出的自动生成控件唯一标识的方案)](https://github.com/yulingtianxia/TBUIAutoTest) 30 | * [Appium Server and Inspector in Desktop GUIs for Mac, Windows, and Linux(Appium GUI工具)](https://github.com/appium/appium-desktop) 31 | 32 | # 让我们开始吧 33 | ## 安装步骤 34 | ### 安装cocoapods 35 | ```bash 36 | $ gem install cocoapods 37 | ``` 38 | ### 修改Podfile 39 | ``` 40 | source 'https://github.com/CocoaPods/Specs.git' 41 | platform :ios, '8.0' 42 | 43 | target 'Example' do 44 | pod 'IQUITestCodeMaker',:git => 'https://github.com/Lobster-King/IQUITestCodeMaker.git',:configurations => ['Debug'] 45 | end 46 | ``` 47 | ### 执行命令 48 | ```bash 49 | $ pod install 50 | ``` 51 | ### IQUITestCodeMaker.h 52 | ```bash 53 | /*注意: 54 | 1.该组件通过运行时自动生成并获取标签,会有一定的性能开销,默认DEBUG模式开启,RELEASE模式关闭。 55 | 2.也可以通过打开#define IQ_UITEST_CODEMAKER_ENABLED 0 强制关闭该组件。 56 | */ 57 | 58 | //#define IQ_UITEST_CODEMAKER_ENABLED 0 59 | 60 | #ifdef IQ_UITEST_CODEMAKER_ENABLED 61 | #define IQ_CODEMAKER_ENABLED IQ_UITEST_CODEMAKER_ENABLED 62 | #else 63 | #define IQ_CODEMAKER_ENABLED DEBUG 64 | #endif 65 | 66 | /*Debug球注意事项: 67 | 1.默认对于Debug球相关UI不做hook操作。 68 | 2.可以通过打开//#define IQ_UITEST_DEBUGBALL_ENABLED 0 强制关闭Debug工具 69 | */ 70 | 71 | //#define IQ_UITEST_DEBUGBALL_ENABLED 0 72 | 73 | #ifdef IQ_UITEST_DEBUGBALL_ENABLED 74 | #define IQ_DEBUGBALL_ENABLED IQ_UITEST_DEBUGBALL_ENABLED 75 | #else 76 | #define IQ_DEBUGBALL_ENABLED DEBUG 77 | #endif 78 | ``` 79 | # 关于Appium 80 | Appium是一个开源的、跨平台的自动化测试框架,目前支持native、hybrid、web、app、模拟器相关的自动化测试。 81 | 82 | 附一链接: 83 | 84 | * [How to install appium ?](https://github.com/appium/appium/blob/master/docs/en/about-appium/getting-started.md) 85 | 86 | # IQUITestCodeMaker路线图 87 | 88 | * 后续支持Macaca等其他主流框架。 89 | * 支持更多脚本语言。 90 | * 多种UI元素定位策略,目前只支持accessibilityIdentifier。 91 | 92 | # 遇到问题? 93 | 94 | 你可以通过如下的方式联系到我 95 | 96 | * 提交PR或者ISSUE。 97 | * 通过Email联系:[zhiwei.geek@gmail.com](mailto:zhiwei.geek@gmail.com) 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /coordinate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lobster-King/IQUITestCodeMaker/78c02850f31ea23aad178a68cff083f1418b2b83/coordinate.png --------------------------------------------------------------------------------