Repository: wix/Detox (js)

Issue:
__DTXDeallocSafeProxy crashes if init with object in middle of deallocation
There's a very common case in our app where we crash due to code in GREYIdlingResourcePrettyPrint.m. We run against Detox 15.4.0.

Internally at Facebook we have an iOS react native module called 'RCTVideoView'. The code happens to do a call to setNeedsLayout during it's dealloc (that's probably not a good idea, but regardless, that's what happens today).

EarlGrey is intercepting calls to setNeedsLayout to track state. 

Detox is intercepting these calls to track state here: https://github.com/wix/Detox/blob/f63ebce0bdc6b96df324a265d617b90a122b5fdd/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m#L74

A call to __DTXDeallocSafeProxy initWithObject happens here, where the object is the view that is currently in the middle of deallocation.

During the init of __DTXDeallocSafeProxy, it tries to store the object into a weak property. https://github.com/wix/Detox/blob/f63ebce0bdc6b96df324a265d617b90a122b5fdd/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m#L45

It looks like this objc_storeWeak throws bad alloc.

From all documentation I can find, objc_storeWeak is supposed to store nil into the weak property if the object is in the middle of deallocation. Though, this code seems to crash anyway. (https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-storeweak)

Any ideas or thoughts on how to fix this?

cc @cs01 

Here is my full stack trace:

```
Signal 4 was raised

(
	0   Detox                               0x000000011a41a984 

_ZL16__DTXHandleCrashP11NSExceptionP8NSNumberP8NSString + 497
	1   Detox                               0x000000011a41a709 _ZL17__DTXHandleSignali + 59
	2   libsystem_platform.dylib            0x00007fff52457b5d _sigtramp + 29
	3   ???                                 0x0000600000f709a0 0x0 + 105553132456352
	4   libobjc.A.dylib                     0x000000011b9fb0c2 _ZN11objc_object27sidetable_clearDeallocatingEv + 66
	5   libobjc.A.dylib                     0x000000011b9f34ca objc_destructInstance + 87
	6   libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	7   libxpc.dylib                        0x0000000123f853b7 -[OS_xpc_object dealloc] + 47
	8   libxpc.dylib                        0x0000000123f75b9d _xpc_dictionary_node_free + 62
	9   libxpc.dylib                        0x0000000123f7320a _xpc_dictionary_dispose + 34
	10  libxpc.dylib                        0x0000000123f69fbf _xpc_dispose + 130
	11  libxpc.dylib                        0x0000000123f85399 -[OS_xpc_object dealloc] + 17
	12  libsystem_trace.dylib               0x0000000123f30bbb _os_activity_stream_reflect + 696
	13  libsystem_trace.dylib               0x0000000123f3c399 _os_log_impl_stream + 564
	14  libsystem_trace.dylib               0x0000000123f3b90f _os_log_impl_flatten_and_send + 6347
	15  libsystem_trace.dylib               0x0000000123f3a035 _os_log + 162
	16  libsystem_trace.dylib               0x0000000123f3c923 _os_log_impl + 9
	17  Detox                               0x000000011a409cfa __dtx_log + 378
	18  Detox                               0x000000011a40a3d5 -[WebSocket sendAction:withParams:withMessageId:] + 317
	19  Detox                               0x000000011a4186cb -[DetoxManager notifyOnCrashWithDetails:] + 254
	20  Detox                               0x000000011a41aa46 _ZL16__DTXHandleCrashP11NSExceptionP8NSNumberP8NSString + 691
	21  Detox                               0x000000011a41a709 _ZL17__DTXHandleSignali + 59
	22  libsystem_platform.dylib            0x00007fff52457b5d _sigtramp + 29
	23  libobjc.A.dylib                     0x000000011ba13020 _ZN12_GLOBAL__N_113SideTablesMapE + 3808
	24  libsystem_kernel.dylib              0x00007fff523cf589 abort_with_payload_wrapper_internal + 0
	25  libobjc.A.dylib                     0x000000011b9fc1b7 _ZL12_objc_fatalvyyPKcP13__va_list_tag + 108
	26  libobjc.A.dylib                     0x000000011b9fc14b _ZL12_objc_fatalvyyPKcP13__va_list_tag + 0
	27  libobjc.A.dylib                     0x000000011b9f92b8 _ZL22defaultBadAllocHandlerP10objc_class + 0
	28  libobjc.A.dylib                     0x000000011b9fa866 objc_storeWeak + 489
	29  Detox                               0x000000011a40cd04 -[__DTXDeallocSafeProxy initWithObject:] + 109
	30  Detox                               0x000000011a40ce4a -[GREYAppStateTracker(PrettyPrint) _pp__trackState:forObject:] + 110
	31  EarlGrey                            0x0000000140e094e3 -[UIView(GREYAdditions) greyswizzled_setNeedsLayout] + 115
	32  Twilight                            0x000000010e8d42be -[RCTVideoView _setState:] + 94
	33  Twilight                            0x000000010e8d57b2 -[RCTVideoView _removePlayer] + 834
	34  Twilight                            0x000000010e8d2b08 -[RCTVideoView _reset] + 72
	35  Twilight                            0x000000010e8d2c9c -[RCTVideoView dealloc] + 44
	36  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	37  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	38  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	39  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	40  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	41  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	42  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	43  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	44  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	45  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	46  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	47  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	48  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	49  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	50  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	51  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	52  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	53  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	54  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	55  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	56  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	57  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	58  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	59  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	60  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	61  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	62  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	63  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	64  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	65  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	66  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	67  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	68  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	69  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	70  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	71  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	72  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	73  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	74  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	75  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	76  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	77  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	78  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	79  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	80  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	81  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	82  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	83  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	84  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	85  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	86  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	87  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	88  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	89  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	90  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	91  libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	92  libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	93  libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	94  libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	95  UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	96  UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	97  UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	98  Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	99  CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	100 libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	101 libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	102 libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	103 libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	104 UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	105 UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	106 UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	107 Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	108 CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	109 libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	110 libobjc.A.dylib                     0x000000011b9f64c0 _object_remove_assocations + 239
	111 libobjc.A.dylib                     0x000000011b9f34c2 objc_destructInstance + 79
	112 libobjc.A.dylib                     0x000000011b9f994f -[NSObject dealloc] + 21
	113 UIKitCore                           0x000000012ec9d6c2 -[UIResponder dealloc] + 145
	114 UIKit                               0x00000001472e495c -[UIResponderAccessibility dealloc] + 55
	115 UIKitCore                           0x000000012f12531c -[UIView dealloc] + 981
	116 Twilight                            0x000000010eaf299f -[RCTView dealloc] + 191
	117 CoreFoundation                      0x000000011d128f7f -[__NSArrayM dealloc] + 415
	118 libobjc.A.dylib                     0x000000011b9fa0d6 _ZN11objc_object17sidetable_releaseEb + 174
	119 libobjc.A.dylib                     0x000000011b9fb75b _ZN19AutoreleasePoolPage12releaseUntilEPP11objc_object + 147
	120 libobjc.A.dylib                     0x000000011b9fb67a objc_autoreleasePoolPop + 199
	121 CoreFoundation                      0x000000011d234396 _CFAutoreleasePoolPop + 22
	122 CoreFoundation                      0x000000011d170cb1 __CFRunLoopRun + 2337
	123 CoreFoundation                      0x000000011d170066 CFRunLoopRunSpecific + 438
	124 GraphicsServices                    0x0000000126db4bb0 GSEventRunModal + 65
	125 Detox                               0x000000011a40f075 __detox_UIApplication_run + 365
	126 UIKitCore                           0x000000012ec70d4d UIApplicationMain + 1621
	127 Twilight                            0x000000010ed4e5c1 main + 113
	128 libdyld.dylib                       0x0000000123c4dc25 start + 1
)`



Pull request description:
Attempt to prevent pretty print crashes
Closes #1947

Patch:
diff --git a/detox/ios/Detox.xcodeproj/project.pbxproj b/detox/ios/Detox.xcodeproj/project.pbxproj
index 7efc47742..d69744b73 100644
--- a/detox/ios/Detox.xcodeproj/project.pbxproj
+++ b/detox/ios/Detox.xcodeproj/project.pbxproj
@@ -57,6 +57,8 @@
 		397EC9B51E7EDE0B00D5F2BB /* EarlGreyStatistics.h in Headers */ = {isa = PBXBuildFile; fileRef = 397EC9B31E7EDE0B00D5F2BB /* EarlGreyStatistics.h */; };
 		397EC9B61E7EDE0B00D5F2BB /* EarlGreyStatistics.m in Sources */ = {isa = PBXBuildFile; fileRef = 397EC9B41E7EDE0B00D5F2BB /* EarlGreyStatistics.m */; };
 		398260E8220CC9530061E83E /* GREYActions+Detox.m in Sources */ = {isa = PBXBuildFile; fileRef = 398260D4220CA56D0061E83E /* GREYActions+Detox.m */; };
+		39999EBA2416339E004E6366 /* __DTXDeallocSafeProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 39999EB82416339E004E6366 /* __DTXDeallocSafeProxy.h */; };
+		39999EBB2416339E004E6366 /* __DTXDeallocSafeProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 39999EB92416339E004E6366 /* __DTXDeallocSafeProxy.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
 		399BF36821933F0C00F96D50 /* ExternalLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 399BF36621933F0C00F96D50 /* ExternalLogging.h */; };
 		399BF36921933F0C00F96D50 /* ExternalLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 399BF36721933F0C00F96D50 /* ExternalLogging.m */; };
 		39A34C711E30F10D00BEBB59 /* DetoxAppDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -242,6 +244,8 @@
 		397EC9B41E7EDE0B00D5F2BB /* EarlGreyStatistics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EarlGreyStatistics.m; sourceTree = "<group>"; };
 		398260CE220CA5380061E83E /* GREYActions+Detox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GREYActions+Detox.h"; sourceTree = "<group>"; };
 		398260D4220CA56D0061E83E /* GREYActions+Detox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "GREYActions+Detox.m"; sourceTree = "<group>"; };
+		39999EB82416339E004E6366 /* __DTXDeallocSafeProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = __DTXDeallocSafeProxy.h; sourceTree = "<group>"; };
+		39999EB92416339E004E6366 /* __DTXDeallocSafeProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = __DTXDeallocSafeProxy.m; sourceTree = "<group>"; };
 		399BF36621933F0C00F96D50 /* ExternalLogging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalLogging.h; sourceTree = "<group>"; };
 		399BF36721933F0C00F96D50 /* ExternalLogging.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExternalLogging.m; sourceTree = "<group>"; };
 		39A34C6F1E30F10D00BEBB59 /* DetoxAppDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetoxAppDelegateProxy.h; sourceTree = "<group>"; };
@@ -420,6 +424,8 @@
 				468731A41E6C6D0500F151BE /* EarlGrey+Detox.m */,
 				397EC9B31E7EDE0B00D5F2BB /* EarlGreyStatistics.h */,
 				397EC9B41E7EDE0B00D5F2BB /* EarlGreyStatistics.m */,
+				39999EB82416339E004E6366 /* __DTXDeallocSafeProxy.h */,
+				39999EB92416339E004E6366 /* __DTXDeallocSafeProxy.m */,
 				391FA5E71E7FD96D0056F82F /* GREYIdlingResourcePrettyPrint.h */,
 				391FA5E81E7FD96D0056F82F /* GREYIdlingResourcePrettyPrint.m */,
 				46A6A6341EF696B600E3AA79 /* GREYConfiguration+Detox.h */,
@@ -494,6 +500,7 @@
 				394767D21DBF98D900D72256 /* WXJSTimerObservationIdlingResource.h in Headers */,
 				390D1C691E3A14EF007F5F46 /* UNNotificationResponse+PrivateHeaders.h in Headers */,
 				391FA5E91E7FD96D0056F82F /* GREYIdlingResourcePrettyPrint.h in Headers */,
+				39999EBA2416339E004E6366 /* __DTXDeallocSafeProxy.h in Headers */,
 				396EA67A22E0AE7D0087E4D4 /* DTXAddressInfo.h in Headers */,
 				39A34C711E30F10D00BEBB59 /* DetoxAppDelegateProxy.h in Headers */,
 				39F642281FDD5EB100468FED /* DTXLogging.h in Headers */,
@@ -718,6 +725,7 @@
 			files = (
 				39F642291FDD5EB100468FED /* DTXLogging.m in Sources */,
 				394767B31DBF987E00D72256 /* TestFailureHandler.m in Sources */,
+				39999EBB2416339E004E6366 /* __DTXDeallocSafeProxy.m in Sources */,
 				394767B71DBF987E00D72256 /* WebSocket.m in Sources */,
 				394767D51DBF98D900D72256 /* WXRunLoopIdlingResource.m in Sources */,
 				468731A61E6C6D0500F151BE /* EarlGrey+Detox.m in Sources */,
diff --git a/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m b/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m
index c92e12bdc..55f05877a 100644
--- a/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m
+++ b/detox/ios/Detox/GREYIdlingResourcePrettyPrint.m
@@ -9,50 +9,7 @@
 #import "GREYIdlingResourcePrettyPrint.h"
 @import ObjectiveC;
 #include <os/log.h>
-
-@interface __DTXDeallocSafeProxy : NSObject
-
-@property (nonatomic, weak) id object;
-
-@end
-
-@implementation __DTXDeallocSafeProxy
-{
-	NSString* _cachedDescription;
-	NSString* _cachedDebugDescription;
-}
-
-- (NSString *)description
-{
-	return  self.object? [self.object description] : _cachedDescription;
-}
-
-- (NSString *)debugDescription
-{
-	return  self.object? [self.object debugDescription] : _cachedDebugDescription;
-}
-
-- (void)dealloc
-{
-	self.object = nil;
-}
-
-- (instancetype)initWithObject:(id)object
-{
-	self = [super init];
-	if(self)
-	{
-		self.object = object;
-		_cachedDescription = [object description];
-		if([object respondsToSelector:@selector(debugDescription)])
-		{
-			_cachedDebugDescription = [object debugDescription];
-		}
-	}
-	return self;
-}
-
-@end
+#import "__DTXDeallocSafeProxy.h"
 
 @interface NSMapTable<KeyType, ObjectType> ()
 
@@ -188,18 +145,13 @@ NSDictionary* _prettyPrintAppStateTracker(GREYAppStateTracker* tracker)
 		NSMutableArray* elems = [NSMutableArray new];
 		NSMutableArray* URLs = [NSMutableArray new];
 		
-		[allElements enumerateObjectsUsingBlock:^(__DTXDeallocSafeProxy* _Nonnull actualElement, NSUInteger idx, BOOL * _Nonnull stop) {
-			__strong id actualObject = actualElement.object;
-			if(actualObject == nil)
-			{
-				return;
-			}
-			
-			[elems addObject:[actualObject description]];
+		[allElements enumerateObjectsUsingBlock:^(__DTXDeallocSafeProxy* _Nonnull proxy, NSUInteger idx, BOOL * _Nonnull stop) {
+			[elems addObject:[proxy description]];
 			
-			if([actualObject isKindOfClass:[NSURLSessionTask class]])
+			NSURLRequest* originalRequest = proxy.originalRequest;
+			if(originalRequest != nil)
 			{
-				[URLs addObject:[(NSURLSessionTask*)actualObject originalRequest].URL.absoluteString];
+				[URLs addObject:originalRequest.URL.absoluteString];
 			}
 		}];
 		
diff --git a/detox/ios/Detox/ReactNativeSupportNoARC.m b/detox/ios/Detox/ReactNativeSupportNoARC.m
index c16218662..211af6932 100644
--- a/detox/ios/Detox/ReactNativeSupportNoARC.m
+++ b/detox/ios/Detox/ReactNativeSupportNoARC.m
@@ -6,6 +6,18 @@
 //  Copyright © 2019 Wix. All rights reserved.
 //
 
+/***
+*    ██╗    ██╗ █████╗ ██████╗ ███╗   ██╗██╗███╗   ██╗ ██████╗
+*    ██║    ██║██╔══██╗██╔══██╗████╗  ██║██║████╗  ██║██╔════╝
+*    ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║  ███╗
+*    ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║   ██║
+*    ╚███╔███╔╝██║  ██║██║  ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
+*     ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝╚═╝  ╚═══╝ ╚═════╝
+*
+*
+* WARNING: This file compiles with ARC disabled! Take extra care when modifying or adding functionality.
+*/
+
 #import "ReactNativeSupportNoARC.h"
 #include <dlfcn.h>
 #include "fishhook.h"
diff --git a/detox/ios/Detox/__DTXDeallocSafeProxy.h b/detox/ios/Detox/__DTXDeallocSafeProxy.h
new file mode 100644
index 000000000..dfa57316c
--- /dev/null
+++ b/detox/ios/Detox/__DTXDeallocSafeProxy.h
@@ -0,0 +1,19 @@
+//
+//  __DTXDeallocSafeProxy.h
+//  Detox
+//
+//  Created by Leo Natan (Wix) on 3/9/20.
+//  Copyright © 2020 Wix. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface __DTXDeallocSafeProxy : NSObject
+
+@property (nonatomic, weak, readonly) id object;
+
+- (instancetype)initWithObject:(id)object;
+
+@property (readonly, copy) NSURLRequest  *originalRequest;
+
+@end
diff --git a/detox/ios/Detox/__DTXDeallocSafeProxy.m b/detox/ios/Detox/__DTXDeallocSafeProxy.m
new file mode 100644
index 000000000..9d392be17
--- /dev/null
+++ b/detox/ios/Detox/__DTXDeallocSafeProxy.m
@@ -0,0 +1,110 @@
+//
+//  __DTXDeallocSafeProxy.m
+//  Detox
+//
+//  Created by Leo Natan (Wix) on 3/9/20.
+//  Copyright © 2020 Wix. All rights reserved.
+//
+
+/***
+*    ██╗    ██╗ █████╗ ██████╗ ███╗   ██╗██╗███╗   ██╗ ██████╗
+*    ██║    ██║██╔══██╗██╔══██╗████╗  ██║██║████╗  ██║██╔════╝
+*    ██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║  ███╗
+*    ██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║   ██║
+*    ╚███╔███╔╝██║  ██║██║  ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
+*     ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝╚═╝  ╚═══╝ ╚═════╝
+*
+*
+* WARNING: This file compiles with ARC disabled! Take extra care when modifying or adding functionality.
+*/
+
+#import "__DTXDeallocSafeProxy.h"
+@import ObjectiveC;
+
+id objc_initWeakOrNil(id *location, id newObj);
+
+@implementation __DTXDeallocSafeProxy
+{
+	NSString* _cachedDescription;
+	NSString* _cachedDebugDescription;
+	
+	NSURLRequest* _cachedOriginalRequest;
+	
+	id _object;
+}
+
+@dynamic object, originalRequest;
+
+- (NSString *)description
+{
+	id object = self.object;
+	
+	return  object ? [object description] : _cachedDescription ?: @"<UNKNOWN OBJECT>";
+}
+
+- (NSString *)debugDescription
+{
+	id object = self.object;
+	
+	return  object ? [object debugDescription] : _cachedDebugDescription ?: @"<UNKNOWN OBJECT>";
+}
+
+- (NSURLRequest *)originalRequest
+{
+	id object = self.object;
+	
+	if(object == nil)
+	{
+		return _cachedOriginalRequest;
+	}
+	
+	return [object respondsToSelector:@selector(originalRequest)] ? [object originalRequest] : nil;
+}
+
+- (id)object
+{
+	return objc_loadWeak(&_object);
+}
+
+- (void)dealloc
+{
+	objc_storeWeak(&_object, nil);
+	
+	[_cachedDescription release];
+	_cachedDescription = nil;
+	[_cachedDebugDescription release];
+	_cachedDebugDescription = nil;
+	
+	[super dealloc];
+}
+
+- (instancetype)initWithObject:(id)object
+{
+	self = [super init];
+	if(self)
+	{
+		objc_initWeakOrNil(&_object, object);
+		
+		_cachedDescription = [[NSString alloc] initWithFormat:@"CACHED: %@", [self.object description]];
+		if([self.object respondsToSelector:@selector(debugDescription)])
+		{
+			_cachedDebugDescription = [[NSString alloc] initWithFormat:@"CACHED: %@", [self.object debugDescription]];
+		}
+		else
+		{
+			_cachedDebugDescription = nil;
+		}
+		
+		if([self.object isKindOfClass:NSURLSessionTask.class])
+		{
+			_cachedOriginalRequest = [[(NSURLSessionTask*)self.object originalRequest] copy];
+		}
+		else
+		{
+			_cachedOriginalRequest = nil;
+		}
+	}
+	return self;
+}
+
+@end
diff --git a/detox/package.json b/detox/package.json
index 00be67896..2d72e5886 100644
--- a/detox/package.json
+++ b/detox/package.json
@@ -1,7 +1,7 @@
 {
   "name": "detox",
   "description": "E2E tests and automation for mobile",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "bin": {
     "detox": "local-cli/cli.js"
   },
diff --git a/detox/src/utils/callsites.js b/detox/src/utils/callsites.js
new file mode 100644
index 000000000..de1fc43e3
--- /dev/null
+++ b/detox/src/utils/callsites.js
@@ -0,0 +1,19 @@
+const path = require('path');
+const cwd = process.cwd() + path.sep;
+
+// Taken from https://github.com/sindresorhus/callsites
+module.exports = () => {
+  const _prepareStackTrace = Error.prepareStackTrace;
+  Error.prepareStackTrace = (_, stack) => stack;
+  const stack = new Error().stack.slice(1);
+  Error.prepareStackTrace = _prepareStackTrace;
+  return stack;
+};
+
+module.exports.stackdump = (endFrame = 0) => {
+  return new Error().stack
+    .split('\n')
+    .slice(2 + endFrame) // 2 = 1 for 'Error:' prefix and 1 for this function's frame
+    .join('\n')
+    .replace(new RegExp(cwd, 'g'), '');
+};
diff --git a/detox/src/utils/customConsoleLogger.js b/detox/src/utils/customConsoleLogger.js
new file mode 100644
index 000000000..a7be782bb
--- /dev/null
+++ b/detox/src/utils/customConsoleLogger.js
@@ -0,0 +1,54 @@
+const util = require('util');
+const path = require('path');
+const callsites = require('./callsites');
+
+const USER_STACK_FRAME_INDEX = 2;
+const CONSOLE_ASSERT_USER_ARGS_INDEX = 1;
+
+function getStackDump() {
+  return callsites.stackdump(USER_STACK_FRAME_INDEX);
+}
+
+function getOrigin() {
+  const userCallsite = callsites()[USER_STACK_FRAME_INDEX];
+  const filename = path.relative(process.cwd(), userCallsite.getFileName());
+  return `at ${filename}:${userCallsite.getLineNumber() || '?'}:${userCallsite.getColumnNumber() || '?'}`;
+}
+
+function override(consoleLevel, bunyanFn) {
+  console[consoleLevel] = (...args) => {
+    bunyanFn({ event: 'USER_LOG' }, getOrigin(), '\n', util.format(...args));
+  };
+}
+
+function overrideTrace(consoleLevel, bunyanFn) {
+  console[consoleLevel] = (...args) => {
+    bunyanFn({ event: 'USER_LOG' }, getOrigin(), '\n  Trace:', util.format(...args), '\n\r' + getStackDump());
+  };
+}
+
+function overrideAssertion(consoleLevel, bunyanFn) {
+  console[consoleLevel] = (...args) => {
+    const [condition] = args;
+    if (!condition) {
+      bunyanFn({ event: 'USER_LOG' }, getOrigin(), '\n  AssertionError:', util.format(...args.slice(CONSOLE_ASSERT_USER_ARGS_INDEX)));
+    }
+  };
+}
+
+function overrideAllLevels(bunyanLogger) {
+  const log = bunyanLogger;
+  override('debug', log.debug.bind(log));
+  override('log', log.info.bind(log));
+  override('warn', log.warn.bind(log));
+  override('error', log.error.bind(log));
+  overrideTrace('trace', log.info.bind(log));
+  overrideAssertion('assert', log.error.bind(log));
+}
+
+module.exports = {
+  override,
+  overrideTrace,
+  overrideAssertion,
+  overrideAllLevels,
+};
diff --git a/detox/src/utils/logger.js b/detox/src/utils/logger.js
index 7929d1807..95cbad4e1 100644
--- a/detox/src/utils/logger.js
+++ b/detox/src/utils/logger.js
@@ -4,6 +4,7 @@ const bunyan = require('bunyan');
 const bunyanDebugStream = require('bunyan-debug-stream');
 const argparse = require('./argparse');
 const temporaryPath = require('../artifacts/utils/temporaryPath');
+const customConsoleLogger = require('./customConsoleLogger');
 
 function adaptLogLevelName(level) {
   switch (level) {
@@ -23,6 +24,11 @@ function adaptLogLevelName(level) {
   }
 }
 
+function overrideConsoleLogger(logger) {
+  const log = logger.child({component: 'USER_LOG'});
+  customConsoleLogger.overrideAllLevels(log);
+}
+
 function createPlainBunyanStream({ logPath, level }) {
   const options = {
     showDate: false,
@@ -97,6 +103,10 @@ function init() {
     logger.plainFileStreamPath = plainFileStreamPath;
   }
 
+  if (argparse.getArgValue('use-custom-logger') === 'true') {
+    overrideConsoleLogger(logger);
+  }
+
   return logger;
 }
 
diff --git a/docs/APIRef.DetoxCLI.md b/docs/APIRef.DetoxCLI.md
index 2c097b531..aba0bebbe 100644
--- a/docs/APIRef.DetoxCLI.md
+++ b/docs/APIRef.DetoxCLI.md
@@ -83,6 +83,7 @@ Initiating your test suite. <sup>[[1]](#notice-passthrough)</sup>
 | --gpu                                         | [Android Only] Launch Emulator with the specific -gpu [gpu mode] parameter. |
 | --device-launch-args | A list of passthrough-arguments to use when (if) devices (Android emulator / iOS simulator) are launched by Detox.<br />**Note: the value must be specified after an equal size (`=`) and inside quotes.** Usage example:<br />`--device-launch-args="-http-proxy http://1.1.1.1:8000 -no-snapshot-load"` |
 | --no-color                                    | Disable colors in log output |
+| --use-custom-logger | Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha).<br />*Default: true* |
 | --help                                        | Show help |
 
 ##### Notices
diff --git a/examples/demo-native-android/package.json b/examples/demo-native-android/package.json
index b3e3764c5..7d83638ad 100644
--- a/examples/demo-native-android/package.json
+++ b/examples/demo-native-android/package.json
@@ -1,6 +1,6 @@
 {
   "name": "detox-demo-native-android",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "scripts": {
     "packager": "react-native start",
@@ -8,7 +8,7 @@
     "e2e": "mocha e2e --opts ./e2e/mocha.opts"
   },
   "devDependencies": {
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "mocha": "^6.1.3"
   },
   "detox": {}
diff --git a/examples/demo-native-ios/package.json b/examples/demo-native-ios/package.json
index ad59caf0a..60e1c181a 100644
--- a/examples/demo-native-ios/package.json
+++ b/examples/demo-native-ios/package.json
@@ -1,9 +1,9 @@
 {
   "name": "detox-demo-native-ios",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "devDependencies": {
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "mocha": "^6.1.3"
   },
   "detox": {
diff --git a/examples/demo-react-native-detox-instruments/package.json b/examples/demo-react-native-detox-instruments/package.json
index a756ef742..3428350fd 100644
--- a/examples/demo-react-native-detox-instruments/package.json
+++ b/examples/demo-react-native-detox-instruments/package.json
@@ -1,10 +1,10 @@
 {
   "name": "demo-react-native-detox-instruments",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "scripts": {},
   "devDependencies": {
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "mocha": "6.x.x"
   },
   "detox": {
diff --git a/examples/demo-react-native-jest/package.json b/examples/demo-react-native-jest/package.json
index e685c1680..58a68e9d2 100644
--- a/examples/demo-react-native-jest/package.json
+++ b/examples/demo-react-native-jest/package.json
@@ -1,6 +1,6 @@
 {
   "name": "demo-react-native-jest",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "scripts": {
     "test:ios-release": "detox test --configuration ios.sim.release -l verbose",
@@ -13,7 +13,7 @@
     "test:jest-circus:android-release-ci": "detox test --configuration android.emu.release -o e2e/config-circus.json -l verbose --workers 2 --headless --record-logs all --take-screenshots all"
   },
   "devDependencies": {
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "jest": "25.1.x",
     "jest-circus": "25.1.x",
     "sanitize-filename": "^1.6.1"
diff --git a/examples/demo-react-native/package.json b/examples/demo-react-native/package.json
index 36653af00..2a8c4cd73 100644
--- a/examples/demo-react-native/package.json
+++ b/examples/demo-react-native/package.json
@@ -1,6 +1,6 @@
 {
   "name": "example",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "scripts": {
     "start": "react-native start",
@@ -23,7 +23,7 @@
     "react-native": "0.60.x"
   },
   "devDependencies": {
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "mocha": "^6.1.3"
   },
   "detox": {
diff --git a/lerna.json b/lerna.json
index 98f6ca877..cc2f3cfc2 100644
--- a/lerna.json
+++ b/lerna.json
@@ -12,7 +12,7 @@
     "generation",
     "."
   ],
-  "version": "15.4.2",
+  "version": "15.4.3",
   "npmClient": "npm",
   "command": {
     "publish": {
diff --git a/package.json b/package.json
index 63c1dea98..ef8520ff1 100644
--- a/package.json
+++ b/package.json
@@ -17,5 +17,5 @@
     "semver": "5.x.x",
     "shell-utils": "1.x.x"
   },
-  "version": "15.4.2"
+  "version": "15.4.3"
 }


Test patch:
diff --git a/detox/local-cli/test.js b/detox/local-cli/test.js
index bba624145..bf19c83a2 100644
--- a/detox/local-cli/test.js
+++ b/detox/local-cli/test.js
@@ -125,7 +125,13 @@ module.exports.builder = {
   'device-launch-args': {
     group: 'Execution:',
     describe: 'Custom arguments to pass (through) onto the device (emulator/simulator) binary when launched.'
-  }
+  },
+  'use-custom-logger': {
+    boolean: true,
+    default: true,
+    group: 'Execution:',
+    describe: `Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha).`,
+  },
 };
 
 const collectExtraArgs = require('./utils/collectExtraArgs')(module.exports.builder);
@@ -224,6 +230,7 @@ module.exports.handler = async function test(program) {
         (hasCustomValue('record-performance') ? `--record-performance ${program.recordPerformance}` : ''),
         (program.artifactsLocation ? `--artifacts-location "${program.artifactsLocation}"` : ''),
         (program.deviceName ? `--device-name "${program.deviceName}"` : ''),
+        (program.useCustomLogger ? `--use-custom-logger "${program.useCustomLogger}"` : ''),
       ]),
       ...getPassthroughArguments(),
     ]).join(' ');
@@ -284,6 +291,7 @@ module.exports.handler = async function test(program) {
         'reportSpecs',
         'readOnlyEmu',
         'deviceLaunchArgs',
+        'useCustomLogger'
       ]),
       DETOX_START_TIMESTAMP: Date.now(),
     };
diff --git a/detox/src/utils/callsites.test.js b/detox/src/utils/callsites.test.js
new file mode 100644
index 000000000..a96454356
--- /dev/null
+++ b/detox/src/utils/callsites.test.js
@@ -0,0 +1,42 @@
+describe('callsites', () => {
+
+  let callsites;
+  beforeEach(() => {
+    callsites = require('./callsites');
+  });
+
+  describe('call-sites query', () => {
+    const callFromWrapperFn = () => callsites();
+
+    it('should return a query-able callsites array', () => {
+      const [callsite0, callsite1] = callFromWrapperFn();
+
+      expect(callsite0.getFileName()).toEqual(expect.stringContaining('src/utils/callsites.test.js'));
+      expect(callsite0.getFunctionName()).toEqual('callFromWrapperFn');
+      expect(callsite0.isToplevel()).toEqual(true);
+      expect(callsite1.getFunctionName()).toEqual('it');
+      expect(callsite1.isToplevel()).toEqual(false);
+    });
+  });
+
+  describe('stack-dump', () => {
+    const callStackDumpFromWrapperFn = (endFrame) => callsites.stackdump(endFrame);
+    const callStackDumpFromTwoWrapperFn = (endFrame) => callStackDumpFromWrapperFn(endFrame);
+
+    const expectedTopFrameRegExp = /^ {4}at callStackDumpFromWrapperFn \(src\/utils\/callsites\.test\.js:[0-9][0-9]?:[0-9][0-9]?\)/;
+    const expected2ndLineRegExp = /^ {4}at callStackDumpFromTwoWrapperFn \(src\/utils\/callsites\.test\.js:[0-9][0-9]?:[0-9][0-9]?\)/;
+
+    it('should return a valid, multi-line, stack-dump string', () => {
+      const stackdump = callStackDumpFromTwoWrapperFn();
+
+      expect(stackdump).toEqual(expect.stringMatching(expectedTopFrameRegExp));
+      expect(stackdump).toEqual(expect.stringMatching(new RegExp(expected2ndLineRegExp, 'm')));
+    });
+
+    it('should slice according to end-frame arg', () => {
+      const _expectedTopLineRegExp = expected2ndLineRegExp;
+      const stackdump = callStackDumpFromTwoWrapperFn(1);
+      expect(stackdump).toEqual(expect.stringMatching(_expectedTopLineRegExp));
+    });
+  });
+});
diff --git a/detox/src/utils/customConsoleLogger.test.js b/detox/src/utils/customConsoleLogger.test.js
new file mode 100644
index 000000000..d12aa5e64
--- /dev/null
+++ b/detox/src/utils/customConsoleLogger.test.js
@@ -0,0 +1,148 @@
+const ignored = expect.anything;
+
+describe('customConsoleLogger', () => {
+  let bunyanLogger;
+
+  beforeEach(() => {
+    jest.unmock('./callsites');
+
+    bunyanLogger = {
+      mock: jest.fn(),
+    };
+  });
+
+  afterEach(() => {
+    delete console.__log;
+  });
+
+  it('should log an event as the prefix', () => {
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log();
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith({ event: 'USER_LOG' }, ignored(), ignored(), ignored());
+  });
+
+  it('should log multiple args', () => {
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log('Testing123');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), ignored(), ignored(), 'Testing123');
+  });
+
+  it('should support string formatting', () => {
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log('Tada! this %s', 'worx');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), ignored(), ignored(), 'Tada! this worx');
+  });
+
+  it('should include log origin', () => {
+    mockCallsites({file: 'mockfilename', line: 'mocklinenumber', col: 'mockcolnumber'});
+
+    const expectedOrigin = 'mockfilename:mocklinenumber:mockcolnumber';
+
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log('');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), expect.stringContaining(expectedOrigin), ignored(), ignored());
+  });
+
+  it('should use relative file-name rather than absolute in origin', () => {
+    const expectedOrigin = 'at src/utils/customConsoleLogger.test.js:';
+
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log('');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), expect.stringContaining(expectedOrigin), ignored(), ignored());
+  });
+
+  it('should handle missing file line/column in origin', () => {
+    mockCallsites({file: 'mockfilename', line: undefined, col: undefined});
+
+    const logger = require('./customConsoleLogger');
+    logger.override('__log', bunyanLogger.mock);
+    console.__log('');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), expect.stringContaining('mockfilename:?:?'), ignored(), ignored());
+  });
+
+  it('should support assertion logging', () => {
+    const logger = require('./customConsoleLogger');
+    logger.overrideAssertion('__log', bunyanLogger.mock);
+    console.__log(false, 'Testing456');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith({ event: 'USER_LOG' }, ignored(), ignored(), 'Testing456');
+  });
+
+  it('should limit assertion logging to false-conditions', () => {
+    const logger = require('./customConsoleLogger');
+    logger.overrideAssertion('__log', bunyanLogger.mock);
+    console.__log(true, 'Testing456');
+
+    expect(bunyanLogger.mock).not.toHaveBeenCalled();
+  });
+
+  it('should support trace-logging', () => {
+    const logger = require('./customConsoleLogger');
+    logger.overrideTrace('__log', bunyanLogger.mock);
+    console.__log('Testing789');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith({ event: 'USER_LOG' }, ignored(), ignored(), 'Testing789', ignored());
+  });
+
+  it('should log stack-trace dump when trace-logging', () => {
+    mockCallsites();
+    const callsites = require('./callsites');
+    callsites.stackdump = jest.fn().mockReturnValue('mockstack\ntrace');
+
+    const logger = require('./customConsoleLogger');
+    logger.overrideTrace('__log', bunyanLogger.mock);
+    console.__log('');
+
+    expect(bunyanLogger.mock).toHaveBeenCalledWith(ignored(), ignored(), ignored(), ignored(), '\n\rmockstack\ntrace');
+  });
+
+  it('should override all levels', () => {
+    const bunyanLogger = {
+      debug: jest.fn(),
+      info: jest.fn(),
+      warn: jest.fn(),
+      error: jest.fn(),
+    };
+
+    const _console = console;
+
+    try {
+      global.console = {};
+
+      const logger = require('./customConsoleLogger');
+      logger.overrideAllLevels(bunyanLogger);
+
+      expect(console.debug).toEqual(expect.any(Function));
+      expect(console.log).toEqual(expect.any(Function));
+      expect(console.warn).toEqual(expect.any(Function));
+      expect(console.error).toEqual(expect.any(Function));
+      expect(console.trace).toEqual(expect.any(Function));
+      expect(console.assert).toEqual(expect.any(Function));
+    } finally {
+      global.console = _console;
+    }
+  });
+
+  const mockCallsites = (callsite) => {
+    const mockCallsite = mockACallsite(callsite);
+    jest.mock('./callsites', () => jest.fn().mockReturnValue([{}, {}, mockCallsite]));
+  };
+
+  const mockACallsite = ({func, file, line, col} = {}) => ({
+    getFunctionName: jest.fn().mockReturnValue(func || ''),
+    getFileName: jest.fn().mockReturnValue(file || ''),
+    getLineNumber: jest.fn().mockReturnValue(line),
+    getColumnNumber: jest.fn().mockReturnValue(col),
+  });
+});
diff --git a/detox/test/package.json b/detox/test/package.json
index b2903def6..2e3d78bd2 100644
--- a/detox/test/package.json
+++ b/detox/test/package.json
@@ -1,6 +1,6 @@
 {
   "name": "detox-test",
-  "version": "15.4.2",
+  "version": "15.4.3",
   "private": true,
   "engines": {
     "node": ">=8.3.0"
@@ -31,7 +31,7 @@
   },
   "devDependencies": {
     "@babel/core": "^7.4.5",
-    "detox": "^15.4.2",
+    "detox": "^15.4.3",
     "express": "^4.15.3",
     "jest": "25.1.x",
     "jest-circus": "25.1.x",