From 58df5a6514e844434f3400905ce31a813301af87 Mon Sep 17 00:00:00 2001 From: Leo Natan Date: Thu, 23 Nov 2017 15:08:38 +0200 Subject: [PATCH] Implement touch visualizers for Detox Closes #426 --- .gitmodules | 3 + detox/ios/COSTouchVisualizer | 1 + detox/ios/Detox.xcodeproj/project.pbxproj | 30 ++++ detox/ios/Detox/DetoxAppDelegateProxy.m | 46 ++++++- .../xcshareddata/xcschemes/example.xcscheme | 1 + detox/test/package.json | 129 +++++++++--------- 6 files changed, 144 insertions(+), 66 deletions(-) create mode 160000 detox/ios/COSTouchVisualizer diff --git a/.gitmodules b/.gitmodules index fa0ee01109..a3e930f444 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ path = detox/ios/EarlGrey url = https://github.com/google/EarlGrey.git ignore = dirty +[submodule "detox/ios/COSTouchVisualizer"] + path = detox/ios/COSTouchVisualizer + url = https://github.com/conopsys/COSTouchVisualizer diff --git a/detox/ios/COSTouchVisualizer b/detox/ios/COSTouchVisualizer new file mode 160000 index 0000000000..ad729c993d --- /dev/null +++ b/detox/ios/COSTouchVisualizer @@ -0,0 +1 @@ +Subproject commit ad729c993de213ccb6be6af30c4bb5730293231c diff --git a/detox/ios/Detox.xcodeproj/project.pbxproj b/detox/ios/Detox.xcodeproj/project.pbxproj index 80b8df559a..c35cdb9869 100644 --- a/detox/ios/Detox.xcodeproj/project.pbxproj +++ b/detox/ios/Detox.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 3928EFBE1E475E4C00C19B6E /* user_notification_calendar_trigger.json in Resources */ = {isa = PBXBuildFile; fileRef = 3928EFBA1E475E4C00C19B6E /* user_notification_calendar_trigger.json */; }; 3928EFBF1E475E4C00C19B6E /* user_notification_push_trigger.json in Resources */ = {isa = PBXBuildFile; fileRef = 3928EFBB1E475E4C00C19B6E /* user_notification_push_trigger.json */; }; 3928EFC21E47673800C19B6E /* LegacyApiAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3928EFC11E47673800C19B6E /* LegacyApiAppDelegate.swift */; }; + 393E66ED1FC5F3F40092EE89 /* COSTouchVisualizer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 393E66E61FC5F3E90092EE89 /* COSTouchVisualizer.framework */; }; 3947679C1DBF985400D72256 /* Detox.h in Headers */ = {isa = PBXBuildFile; fileRef = 3947679A1DBF985400D72256 /* Detox.h */; settings = {ATTRIBUTES = (Public, ); }; }; 394767AE1DBF987E00D72256 /* DetoxManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 394767A41DBF987E00D72256 /* DetoxManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; 394767AF1DBF987E00D72256 /* DetoxManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 394767A51DBF987E00D72256 /* DetoxManager.m */; }; @@ -85,6 +86,13 @@ remoteGlobalIDString = 394767961DBF985400D72256; remoteInfo = Detox; }; + 393E66E51FC5F3E90092EE89 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 393E66E11FC5F3E90092EE89 /* COSTouchVisualizer.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A71946A71C7EF890003B7C4A; + remoteInfo = COSTouchVisualizer; + }; 394767DB1DBF991E00D72256 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 394767D71DBF991E00D72256 /* EarlGrey.xcodeproj */; @@ -182,6 +190,7 @@ 3928EFBA1E475E4C00C19B6E /* user_notification_calendar_trigger.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = user_notification_calendar_trigger.json; sourceTree = ""; }; 3928EFBB1E475E4C00C19B6E /* user_notification_push_trigger.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = user_notification_push_trigger.json; sourceTree = ""; }; 3928EFC11E47673800C19B6E /* LegacyApiAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyApiAppDelegate.swift; sourceTree = ""; }; + 393E66E11FC5F3E90092EE89 /* COSTouchVisualizer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = COSTouchVisualizer.xcodeproj; path = COSTouchVisualizer/Classes/COSTouchVisualizer.xcodeproj; sourceTree = ""; }; 394767971DBF985400D72256 /* Detox.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Detox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3947679A1DBF985400D72256 /* Detox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Detox.h; sourceTree = ""; }; 3947679B1DBF985400D72256 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -239,6 +248,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 393E66ED1FC5F3F40092EE89 /* COSTouchVisualizer.framework in Frameworks */, 394767F71DBF994500D72256 /* EarlGrey.framework in Frameworks */, 394767F61DBF994200D72256 /* SocketRocket.framework in Frameworks */, ); @@ -284,6 +294,14 @@ name = UserNotificationTestInputs; sourceTree = ""; }; + 393E66E21FC5F3E90092EE89 /* Products */ = { + isa = PBXGroup; + children = ( + 393E66E61FC5F3E90092EE89 /* COSTouchVisualizer.framework */, + ); + name = Products; + sourceTree = ""; + }; 3947678D1DBF985400D72256 = { isa = PBXGroup; children = ( @@ -371,6 +389,7 @@ 394767D61DBF990F00D72256 /* Frameworks */ = { isa = PBXGroup; children = ( + 393E66E11FC5F3E90092EE89 /* COSTouchVisualizer.xcodeproj */, 394767DD1DBF992400D72256 /* SocketRocket.xcodeproj */, 394767D71DBF991E00D72256 /* EarlGrey.xcodeproj */, ); @@ -509,6 +528,10 @@ productRefGroup = 394767981DBF985400D72256 /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 393E66E21FC5F3E90092EE89 /* Products */; + ProjectRef = 393E66E11FC5F3E90092EE89 /* COSTouchVisualizer.xcodeproj */; + }, { ProductGroup = 394767D81DBF991E00D72256 /* Products */; ProjectRef = 394767D71DBF991E00D72256 /* EarlGrey.xcodeproj */; @@ -528,6 +551,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 393E66E61FC5F3E90092EE89 /* COSTouchVisualizer.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = COSTouchVisualizer.framework; + remoteRef = 393E66E51FC5F3E90092EE89 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 394767DC1DBF991E00D72256 /* EarlGrey.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; diff --git a/detox/ios/Detox/DetoxAppDelegateProxy.m b/detox/ios/Detox/DetoxAppDelegateProxy.m index b47ad1b260..fb638312c0 100644 --- a/detox/ios/Detox/DetoxAppDelegateProxy.m +++ b/detox/ios/Detox/DetoxAppDelegateProxy.m @@ -10,16 +10,44 @@ @import ObjectiveC; @import UIKit; @import UserNotifications; +@import COSTouchVisualizer; #import @class DetoxAppDelegateProxy; static DetoxAppDelegateProxy* _currentAppDelegateProxy; +static COSTouchVisualizerWindow* _touchVisualizerWindow; + +@interface UIWindow (DTXEventProxy) @end + +@implementation UIWindow (DTXEventProxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method m1 = class_getInstanceMethod(self, @selector(sendEvent:)); + Method m2 = class_getInstanceMethod(self, @selector(__dtx_sendEvent:)); + method_exchangeImplementations(m1, m2); + }); +} + +- (void)__dtx_sendEvent:(UIEvent *)event +{ + if([self isKindOfClass:[COSTouchVisualizerWindow class]]) + { + return; + } + + [_touchVisualizerWindow sendEvent:event]; + [self __dtx_sendEvent:event]; +} -@interface DetoxAppDelegateProxy () @end +@interface DetoxAppDelegateProxy () @end + @implementation DetoxAppDelegateProxy + (instancetype)currentAppDelegateProxy @@ -63,7 +91,7 @@ + (void)load { Method m = class_getInstanceMethod([UIApplication class], @selector(setDelegate:)); void (*orig)(id, SEL, id) = (void*)method_getImplementation(m); - method_setImplementation(m, imp_implementationWithBlock(^ (id _self, id origDelegate) { + method_setImplementation(m, imp_implementationWithBlock(^ (id _self, id origDelegate) { //Only create a dupe class if the provided instance is not already a dupe class. if(origDelegate != nil && [origDelegate respondsToSelector:@selector(__dtx_canaryInTheCoalMine)] == NO) { @@ -92,6 +120,15 @@ - (void)__dtx_canaryInTheCoalMine {} - (void)__dtx_applicationDidLaunchNotification:(NSNotification*)notification { [self.__dtx_userNotificationDispatcher dispatchOnAppDelegate:self simulateDuringLaunch:YES]; + + dispatch_async(dispatch_get_main_queue(), ^{ + _touchVisualizerWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + _touchVisualizerWindow.windowLevel = 100000000000; + _touchVisualizerWindow.backgroundColor = [UIColor.greenColor colorWithAlphaComponent:0.0]; + _touchVisualizerWindow.hidden = NO; + _touchVisualizerWindow.touchVisualizerWindowDelegate = self; + _touchVisualizerWindow.userInteractionEnabled = NO; + }); } - (NSURL*)_userNotificationDataURL @@ -189,4 +226,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( return rv; } +- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window +{ + return YES; +} + @end diff --git a/detox/test/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme b/detox/test/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme index cf4ca18ccc..b40849a795 100644 --- a/detox/test/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme +++ b/detox/test/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme @@ -60,6 +60,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" diff --git a/detox/test/package.json b/detox/test/package.json index a4a349b7f7..a7dd80e825 100644 --- a/detox/test/package.json +++ b/detox/test/package.json @@ -1,65 +1,66 @@ { - "name": "detox-test", - "version": "0.0.1", - "private": true, - "scripts": { - "test": ":", - "packager": "react-native start", - "detox-server": "detox run-server", - "e2e:ios": "detox test --configuration ios.sim.release --debug-synchronization 10000", - "e2e:android": "detox test --configuration android.emu.release --loglevel verbose", - "build:ios": "detox build --configuration ios.sim.release", - "build:android": "detox build --configuration android.emu.release" - }, - "dependencies": { - "react": "^16.0.0-beta.5", - "react-native": "^0.49.3" - }, - "devDependencies": { - "detox": "^5.0.0", - "express": "^4.15.3", - "lodash": "^4.14.1", - "mocha": "^4.0.0" - }, - "detox": { - "specs": "e2e", - "__session": { - "server": "ws://localhost:8099", - "sessionId": "test" - }, - "configurations": { - "ios.sim.debug": { - "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app", - "build": "set -o pipefail && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", - "type": "ios.simulator", - "name": "iPhone 7 Plus" - }, - "ios.sim.release": { - "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app", - "build": "set -o pipefail && export CODE_SIGNING_REQUIRED=NO && export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Release -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", - "type": "ios.simulator", - "name": "iPhone 7 Plus" - }, - "ios.none": { - "type": "ios.none", - "name": "iPhone 7 Plus", - "session": { - "server": "ws://localhost:8099", - "sessionId": "test" - } - }, - "android.emu.debug": { - "binaryPath": "android/app/build/outputs/apk/app-debug.apk", - "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", - "type": "android.emulator", - "name": "Nexus_5X_API_24" - }, - "android.emu.release": { - "binaryPath": "android/app/build/outputs/apk/app-release.apk", - "build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..", - "type": "android.emulator", - "name": "Nexus_5X_API_26" - } - } - } -} + "name": "detox-test", + "version": "0.0.1", + "private": true, + "scripts": { + "test": ":", + "packager": "react-native start", + "detox-server": "detox run-server", + "e2e:ios": "detox test --configuration ios.sim.release --debug-synchronization 10000", + "e2e:android": "detox test --configuration android.emu.release --loglevel verbose", + "build:ios": "detox build --configuration ios.sim.release", + "build:android": "detox build --configuration android.emu.release" + }, + "dependencies": { + "react": "^16.0.0-beta.5", + "react-native": "^0.49.3" + }, + "devDependencies": { + "detox": "^5.0.0", + "express": "^4.15.3", + "lodash": "^4.14.1", + "mocha": "^4.0.0" + }, + "detox": { + "specs": "e2e", + "__session": { + "server": "ws://localhost:8099", + "sessionId": "test" + }, + "configurations": { + "ios.sim.debug": { + "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app", + "build": "set -o pipefail && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", + "type": "ios.simulator", + "name": "iPhone 7 Plus" + }, + "ios.sim.release": { + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app", + "build": "set -o pipefail && export CODE_SIGNING_REQUIRED=NO && export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -scheme example_ci -configuration Release -sdk iphonesimulator -derivedDataPath ios/build | xcpretty", + "type": "ios.simulator", + "name": "iPhone 7 Plus" + }, + "ios.none": { + "binaryPath": "ios", + "type": "ios.none", + "name": "iPhone 7 Plus", + "session": { + "server": "ws://localhost:8099", + "sessionId": "test" + } + }, + "android.emu.debug": { + "binaryPath": "android/app/build/outputs/apk/app-debug.apk", + "build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", + "type": "android.emulator", + "name": "Nexus_5X_API_24" + }, + "android.emu.release": { + "binaryPath": "android/app/build/outputs/apk/app-release.apk", + "build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..", + "type": "android.emulator", + "name": "Nexus_5X_API_26" + } + } + } +} \ No newline at end of file