From bba3569d884f367b5b450e8bac34053ccc9a7f00 Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Sun, 28 Jan 2024 14:32:02 +0200
Subject: [PATCH 01/11] feat(android): support repro-steps for button
Support extracting the label of the button in repro-steps via TouchedViewExtractor
---
.../RNInstabugReactnativeModule.java | 4 +
.../utils/RNTouchedViewExtractor.java | 133 ++++++++++++++++++
2 files changed, 137 insertions(+)
create mode 100644 android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java
index 32bf02a30f..514b98f7d2 100644
--- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java
+++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java
@@ -27,6 +27,7 @@
import com.instabug.library.IssueType;
import com.instabug.library.LogLevel;
import com.instabug.library.ReproConfigurations;
+import com.instabug.library.core.InstabugCore;
import com.instabug.library.internal.module.InstabugLocale;
import com.instabug.library.invocation.InstabugInvocationEvent;
import com.instabug.library.logging.InstabugLog;
@@ -37,6 +38,7 @@
import com.instabug.reactlibrary.utils.EventEmitterModule;
import com.instabug.reactlibrary.utils.MainThreadHandler;
+import com.instabug.reactlibrary.utils.RNTouchedViewExtractor;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
@@ -133,6 +135,8 @@ public void init(
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
+ final RNTouchedViewExtractor rnTouchedViewExtractor = new RNTouchedViewExtractor();
+ InstabugCore.setTouchedViewExtractorExtension(rnTouchedViewExtractor);
final ArrayList keys = ArrayUtil.parseReadableArrayOfStrings(invocationEventValues);
final ArrayList parsedInvocationEvents = ArgsRegistry.invocationEvents.getAll(keys);
final InstabugInvocationEvent[] invocationEvents = parsedInvocationEvents.toArray(new InstabugInvocationEvent[0]);
diff --git a/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java b/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
new file mode 100644
index 0000000000..ee4ccc53a6
--- /dev/null
+++ b/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
@@ -0,0 +1,133 @@
+package com.instabug.reactlibrary.utils;
+
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.facebook.react.views.view.ReactViewGroup;
+import com.instabug.library.core.InstabugCore;
+import com.instabug.library.visualusersteps.TouchedView;
+import com.instabug.library.visualusersteps.TouchedViewExtractor;
+
+public class RNTouchedViewExtractor implements TouchedViewExtractor {
+ @Override
+ public boolean getShouldDependOnNative() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public TouchedView extract(@NonNull View view, @NonNull TouchedView touchedView) {
+ ReactViewGroup reactViewGroup = findReactButtonViewGroup(view);
+ if (reactViewGroup == null) return null;
+ return getExtractionStrategy(reactViewGroup).extract(reactViewGroup, touchedView);
+ }
+
+ @Nullable
+ private ReactViewGroup findReactButtonViewGroup(@NonNull View startView) {
+ if (isReactButtonViewGroup(startView)) return (ReactViewGroup) startView;
+ ViewParent currentParent = startView.getParent();
+ int iteratorIndex = 0;
+ do {
+ if (currentParent == null || isReactButtonViewGroup(currentParent))
+ return (ReactViewGroup) currentParent;
+ currentParent = currentParent.getParent();
+ iteratorIndex++;
+ } while (iteratorIndex < 2);
+ return null;
+ }
+
+ private boolean isReactButtonViewGroup(@NonNull View view) {
+ return (view instanceof ReactViewGroup) && view.isFocusable() && view.isClickable();
+ }
+
+ private boolean isReactButtonViewGroup(@NonNull ViewParent viewParent) {
+ if (!(viewParent instanceof ReactViewGroup)) return false;
+ ViewGroup group = (ReactViewGroup) viewParent;
+ return group.isFocusable() && group.isClickable();
+ }
+
+ private ReactButtonExtractionStrategy getExtractionStrategy(ReactViewGroup reactButton){
+ int labelsCount = 0;
+ int groupsCount = 0;
+ for (int index=0; index < reactButton.getChildCount(); index++){
+ View currentView = reactButton.getChildAt(index);
+ if (currentView instanceof TextView) {
+
+ labelsCount++;
+ continue;
+ }
+ if (currentView instanceof ViewGroup) {
+ groupsCount++;
+ }
+ }
+ if (labelsCount > 1 || groupsCount > 0) return new MultiLabelsExtractionStrategy();
+ if (labelsCount == 1) return new SingleLabelExtractionStrategy();
+ return new NoLabelsExtractionStrategy();
+ }
+
+ interface ReactButtonExtractionStrategy {
+ @Nullable
+ TouchedView extract(ViewGroup reactButton, TouchedView touchedView);
+ }
+
+ class MultiLabelsExtractionStrategy implements ReactButtonExtractionStrategy {
+ private final String MULTI_LABEL_BUTTON_PRE_STRING = "A button that contains \"%s\"";
+
+ @Override
+ @Nullable
+ public TouchedView extract(ViewGroup reactButton, TouchedView touchedView) {
+
+ touchedView.setProminentLabel(
+ InstabugCore.composeProminentLabelForViewGroup(reactButton, MULTI_LABEL_BUTTON_PRE_STRING)
+ );
+ return touchedView;
+ }
+ }
+
+ class SingleLabelExtractionStrategy implements ReactButtonExtractionStrategy {
+
+ @Override
+ public TouchedView extract(ViewGroup reactButton, TouchedView touchedView) {
+ TextView targetLabel = null;
+ for (int index = 0; index < reactButton.getChildCount(); index++) {
+ View currentView = reactButton.getChildAt(index);
+ if (!(currentView instanceof TextView)) continue;
+ targetLabel = (TextView) currentView;
+ break;
+ }
+ if (targetLabel == null) return touchedView;
+
+ String labelText = getLabelText(targetLabel);
+ touchedView.setProminentLabel(InstabugCore.composeProminentLabelFor(labelText, false));
+ return touchedView;
+ }
+
+ @Nullable
+ private String getLabelText(TextView textView) {
+ String labelText = null;
+ if (!TextUtils.isEmpty(textView.getText())) {
+ labelText = textView.getText().toString();
+ } else if (!TextUtils.isEmpty(textView.getContentDescription())) {
+ labelText = textView.getContentDescription().toString();
+ }
+ return labelText;
+ }
+ }
+
+ class NoLabelsExtractionStrategy implements ReactButtonExtractionStrategy {
+
+ @Override
+ public TouchedView extract(ViewGroup reactButton, TouchedView touchedView) {
+ touchedView.setProminentLabel(
+ InstabugCore.composeProminentLabelFor(null, false)
+ );
+ return touchedView;
+ }
+ }
+}
From ae914ab6f494375ff1418609c57be7e0f056904f Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Sun, 28 Jan 2024 17:35:44 +0200
Subject: [PATCH 02/11] feat(example): add repro-steps button samples
Added buttons for testing purposes.
---
examples/default/src/App.tsx | 6 ++-
.../user-steps/BasicComponentsScreen.tsx | 47 +++++++++++++++++--
2 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/examples/default/src/App.tsx b/examples/default/src/App.tsx
index 6dff03618d..7f302c5d1e 100644
--- a/examples/default/src/App.tsx
+++ b/examples/default/src/App.tsx
@@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { NavigationContainer } from '@react-navigation/native';
-import Instabug, { InvocationEvent, LogLevel } from 'instabug-reactnative';
+import Instabug, { InvocationEvent, LogLevel, ReproStepsMode } from 'instabug-reactnative';
import { NativeBaseProvider } from 'native-base';
import { RootTabNavigator } from './navigation/RootTab';
@@ -17,6 +17,10 @@ export const App: React.FC = () => {
invocationEvents: [InvocationEvent.floatingButton],
debugLogsLevel: LogLevel.verbose,
});
+
+ Instabug.setReproStepsConfig({
+ all: ReproStepsMode.enabled,
+ });
}, []);
return (
diff --git a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
index 16aa55b8c5..4cc53be3cb 100644
--- a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
+++ b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
@@ -10,6 +10,7 @@ import {
Switch,
useWindowDimensions,
ActivityIndicator,
+ View,
} from 'react-native';
import Slider from '@react-native-community/slider';
import { Center, HStack, ScrollView, VStack } from 'native-base';
@@ -17,10 +18,11 @@ import { Center, HStack, ScrollView, VStack } from 'native-base';
import { Screen } from '../../components/Screen';
import { Section } from '../../components/Section';
import { nativeBaseTheme } from '../../theme/nativeBaseTheme';
+import Icon from 'react-native-vector-icons/Ionicons';
import { InputField } from '../../components/InputField';
/**
- * A screen that demonstates the usage of user steps with basic React Native components.
+ * A screen that demonstrates the usage of user steps with basic React Native components.
*
* This specific screen doesn't use NativeBase in some parts since we need to focus on
* capturing React Native provided components rather than implementations built on top of it.
@@ -62,13 +64,51 @@ export const BasicComponentsScreen: React.FC = () => {
-
- Pressable Button
+
+
+
+ Icon Pressable Button
+
Touchable Button
+
+
+ Touchable Button
+ Multiple Texts
+
+
+
+ Pressable Button
+
+
+
+ Icon Pressable Button
+
+
+
+
+
+ Icon Button
+
+
+
+ 1 Tier
+
+
+
+ 4 Tier Nested Button
+
+
+
+
@@ -129,6 +169,7 @@ const styles = StyleSheet.create({
formControlStyles.formControl,
{
backgroundColor: nativeBaseTheme.colors.primary[600],
+ justifyContent: 'center',
},
]),
});
From 59933ded9793c08282739006839be571839e4752 Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Sun, 28 Jan 2024 22:06:23 +0200
Subject: [PATCH 03/11] feat: Support masking in example components
---
.../user-steps/BasicComponentsScreen.tsx | 254 ++++++++++++------
1 file changed, 170 insertions(+), 84 deletions(-)
diff --git a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
index 4cc53be3cb..d4525f6877 100644
--- a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
+++ b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
import {
Alert,
Button,
@@ -11,9 +11,11 @@ import {
useWindowDimensions,
ActivityIndicator,
View,
+ TextInput,
} from 'react-native';
import Slider from '@react-native-community/slider';
import { Center, HStack, ScrollView, VStack } from 'native-base';
+import Instabug from 'instabug-reactnative';
import { Screen } from '../../components/Screen';
import { Section } from '../../components/Section';
@@ -37,100 +39,184 @@ export const BasicComponentsScreen: React.FC = () => {
};
};
+ function maskAllViews() {
+ // Mask Text
+ Instabug.addPrivateView(textRef.current!);
+
+ // Mask Image
+ Instabug.addPrivateView(imageRef.current!);
+
+ // Mask TextInput
+ Instabug.addPrivateView(textInputRef.current!);
+
+ // Mask Button
+ Instabug.addPrivateView(buttonRef.current!);
+
+ // Mask Pressable
+ Instabug.addPrivateView(pressableRef.current!);
+
+ // Mask TouchableOpacity
+ Instabug.addPrivateView(touchableOpacityRef.current!);
+
+ // Mask MultiText touchable opacity Button
+ Instabug.addPrivateView(multiTextTouchableOpacityRef.current!);
+
+ // Mask Icon Pressable Button
+ Instabug.addPrivateView(iconPressableButtonRef.current!);
+
+ // Mask Icon.Button
+ Instabug.addPrivateView(iconButtonRef.current!);
+
+ // Mask A four TouchableOpacity Button
+ Instabug.addPrivateView(fourTierTouchableOpacityButtonRef.current!);
+
+ // Mask Switch Button
+ Instabug.addPrivateView(switchButtonRef.current!);
+
+ // Mask slider
+ Instabug.addPrivateView(sliderRef.current!);
+
+ // Mask slider
+ Instabug.addPrivateView(activityIndicatorRef.current!);
+ }
+
+ const textRef = useRef(null);
+ const imageRef = useRef(null);
+ const textInputRef = useRef(null);
+ const buttonRef = useRef
*
* @return {@code true} if the native Android SDK should depend on native extraction,
- * {@code false} otherwise.
+ * {@code false} otherwise.
*/
@Override
public boolean getShouldDependOnNative() {
From aa65b2af356bb24ad01617f80154c6921c5b8a6a Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Tue, 6 Feb 2024 13:30:04 +0200
Subject: [PATCH 09/11] refactor(example): improve four tier button
Add one more Tier between the 1st Tier Text and the 4th tier text (as you click on the text itself, so there has to be enough tiers between them).
---
.../default/src/screens/user-steps/BasicComponentsScreen.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
index 0c14ce9990..6a3ded5686 100644
--- a/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
+++ b/examples/default/src/screens/user-steps/BasicComponentsScreen.tsx
@@ -177,7 +177,9 @@ export const BasicComponentsScreen: React.FC = () => {
- 4th Tier Nested Button
+
+ 4th Tier Nested Button
+
From 1b07437cdba5dbebb2478c209476540d40e242f3 Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Sat, 10 Feb 2024 06:58:26 +0200
Subject: [PATCH 10/11] chore(android): Remove redundant NoLabels Extraction
---
.../instabug/reactlibrary/utils/RNTouchedViewExtractor.java | 4 ----
1 file changed, 4 deletions(-)
diff --git a/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java b/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
index 5d38c58bd8..b500bd65cb 100644
--- a/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
+++ b/android/src/main/java/com/instabug/reactlibrary/utils/RNTouchedViewExtractor.java
@@ -159,12 +159,8 @@ private String getLabelText(ReactTextView textView) {
}
class NoLabelsExtractionStrategy implements ReactButtonExtractionStrategy {
-
@Override
public TouchedView extract(ReactViewGroup reactButton, TouchedView touchedView) {
- touchedView.setProminentLabel(
- InstabugCore.composeProminentLabelFor(null, false)
- );
return touchedView;
}
}
From 479d7222c2680be08a699804b78ece060c192fc1 Mon Sep 17 00:00:00 2001
From: AbdElHamid Nasser
Date: Sat, 10 Feb 2024 07:02:17 +0200
Subject: [PATCH 11/11] docs: update changelog.md
---
CHANGELOG.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 936f525a51..5885120e21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@
### Added
-- Support user identification using ID ([#1115](https://github.com/Instabug/Instabug-React-Native/pull/1115))
+- Support user identification using ID ([#1115](https://github.com/Instabug/Instabug-React-Native/pull/1115)).
+- Support button detection and label extraction for repro steps ([#1109](https://github.com/Instabug/Instabug-React-Native/pull/1109)).
### Changed