diff --git a/.circleci/config.yml b/.circleci/config.yml index 67e56d8fa..ffeab99da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ commands: setup_flutter: steps: - flutter/install_sdk_and_pub: - version: 3.10.5 + version: 3.24.0 - run: name: Generate Pigeons command: sh ./scripts/pigeon.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 98aa7239f..950d295b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [14.3.0](https://github.com/Instabug/Instabug-Flutter/compare/v14.1.0...14.3.0) (April 21, 2025) + +### Added + +- Add support enable/disable capturing network body. ([#561](https://github.com/Instabug/Instabug-Flutter/pull/561)) + +### Changed + +- Bump Instabug iOS SDK to v14.3.0 ([#569](https://github.com/Instabug/Instabug-Flutter/pull/569)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/14.3.0). + +- Bump Instabug Android SDK to v14.3.0 ([#569](https://github.com/Instabug/Instabug-Flutter/pull/569)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v14.3.0). + + +### Fixed + +- Fixed an issue with `SetReproStepsConfig` on Android platform ([#543](https://github.com/Instabug/Instabug-Flutter/pull/543)). + + +## [14.1.0](https://github.com/Instabug/Instabug-Flutter/compare/v14.0.0...v14.1.0) (January 2, 2025) + +### Changed + +- Bump Instabug Android SDK to v14.1.0 ([#539](https://github.com/Instabug/Instabug-Flutter/pull/539)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v14.1.0). +- Bump Instabug iOS SDK to v14.1.0 ([#539](https://github.com/Instabug/Instabug-Flutter/pull/539)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/14.1.0), + ## [14.1.0](https://github.com/Instabug/Instabug-Flutter/compare/v14.0.0...v14.1.0) (January 2, 2025) ### Changed diff --git a/android/build.gradle b/android/build.gradle index e3b5d0bca..a6c41ffd0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group 'com.instabug.flutter' -version '14.1.0' +version '14.3.0' buildscript { repositories { @@ -44,7 +44,7 @@ android { } dependencies { - api 'com.instabug.library:instabug:14.1.0' + api 'com.instabug.library:instabug:14.3.0' testImplementation 'junit:junit:4.13.2' testImplementation "org.mockito:mockito-inline:3.12.1" testImplementation "io.mockk:mockk:1.13.13" diff --git a/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java b/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java index cd1e018cb..bb3b043fa 100644 --- a/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java +++ b/android/src/main/java/com/instabug/flutter/InstabugFlutterPlugin.java @@ -27,7 +27,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry.Registrar; public class InstabugFlutterPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = InstabugFlutterPlugin.class.getName(); @@ -35,14 +34,6 @@ public class InstabugFlutterPlugin implements FlutterPlugin, ActivityAware { @SuppressLint("StaticFieldLeak") private static Activity activity; - /** - * Embedding v1 - */ - @SuppressWarnings("deprecation") - public static void registerWith(Registrar registrar) { - activity = registrar.activity(); - register(registrar.context().getApplicationContext(), registrar.messenger(), (FlutterRenderer) registrar.textures()); - } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { diff --git a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java index 1ed5f825d..607c569a4 100644 --- a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java @@ -33,6 +33,14 @@ public static void init(BinaryMessenger messenger) { ApmPigeon.ApmHostApi.setup(messenger, api); } + /** + * The function sets the enabled status of APM. + * + * @param isEnabled The `setEnabled` method in the code snippet is used to enable or disable a + * feature, and it takes a `Boolean` parameter named `isEnabled`. When this method is called with + * `true`, it enables the feature, and when called with `false`, it disables the feature. The method + * internally calls + */ @Override public void setEnabled(@NonNull Boolean isEnabled) { try { @@ -42,6 +50,13 @@ public void setEnabled(@NonNull Boolean isEnabled) { } } + /** + * Sets the cold app launch enabled status using the APM library. + * + * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether cold app launch + * is enabled or not. When `isEnabled` is set to `true`, cold app launch is enabled, and when it is set + * to `false`, cold app launch is disabled. + */ @Override public void setColdAppLaunchEnabled(@NonNull Boolean isEnabled) { try { @@ -51,6 +66,14 @@ public void setColdAppLaunchEnabled(@NonNull Boolean isEnabled) { } } + /** + * The function sets the auto UI trace enabled status in an APM system, handling any exceptions that + * may occur. + * + * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether the Auto UI + * trace feature should be enabled or disabled. When `isEnabled` is set to `true`, the Auto UI trace + * feature is enabled, and when it is set to `false`, the feature is disabled. + */ @Override public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) { try { @@ -60,6 +83,21 @@ public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) { } } + /** + * Starts an execution trace and handles the result + * using callbacks. + * + * @param id The `id` parameter is a non-null String that represents the identifier of the execution + * trace. + * @param name The `name` parameter in the `startExecutionTrace` method represents the name of the + * execution trace that will be started. It is used as a reference to identify the trace during + * execution monitoring. + * @param result The `result` parameter in the `startExecutionTrace` method is an instance of + * `ApmPigeon.Result`. This parameter is used to provide the result of the execution trace + * operation back to the caller. The `success` method of the `result` object is called with the + * + * @deprecated see {@link #startFlow} + */ @Override public void startExecutionTrace(@NonNull String id, @NonNull String name, ApmPigeon.Result result) { ThreadManager.runOnBackground( @@ -100,6 +138,17 @@ public void run() { ); } + /** + * Starts an AppFlow with the specified name. + *
+ * On starting two flows with the same name the older flow will end with force abandon end reason. + * AppFlow name cannot exceed 150 characters otherwise it's truncated, + * leading and trailing whitespaces are also ignored. + * + * @param name AppFlow name. It can not be empty string or null. + * Starts a new AppFlow, if APM is enabled, feature is enabled + * and Instabug SDK is initialized. + */ @Override public void startFlow(@NonNull String name) { try { @@ -109,6 +158,26 @@ public void startFlow(@NonNull String name) { } } + /** + * Sets custom attributes for AppFlow with a given name. + *
+ * Setting an attribute value to null will remove its corresponding key if it already exists. + *
+ * Attribute key name cannot exceed 30 characters. + * Leading and trailing whitespaces are also ignored. + * Does not accept empty strings or null. + *
+ * Attribute value name cannot exceed 60 characters, + * leading and trailing whitespaces are also ignored. + * Does not accept empty strings. + *
+ * If a trace is ended, attributes will not be added and existing ones will not be updated. + *
+ * + * @param name AppFlow name. It can not be empty string or null + * @param key AppFlow attribute key. It can not be empty string or null + * @param value AppFlow attribute value. It can not be empty string. Null to remove attribute + */ @Override public void setFlowAttribute(@NonNull String name, @NonNull String key, @Nullable String value) { try { @@ -118,6 +187,11 @@ public void setFlowAttribute(@NonNull String name, @NonNull String key, @Nullabl } } + /** + * Ends AppFlow with a given name. + * + * @param name AppFlow name to be ended. It can not be empty string or null + */ @Override public void endFlow(@NonNull String name) { try { @@ -127,6 +201,15 @@ public void endFlow(@NonNull String name) { } } + /** + * Adds a new attribute to trace + * + * @param id String id of the trace. + * @param key attribute key + * @param value attribute value. Null to remove attribute + * + * @deprecated see {@link #setFlowAttribute} + */ @Override public void setExecutionTraceAttribute(@NonNull String id, @NonNull String key, @NonNull String value) { try { @@ -136,6 +219,13 @@ public void setExecutionTraceAttribute(@NonNull String id, @NonNull String key, } } + /** + * Ends a trace + * + * @param id string id of the trace. + * + * @deprecated see {@link #endFlow} + */ @Override public void endExecutionTrace(@NonNull String id) { try { @@ -145,6 +235,11 @@ public void endExecutionTrace(@NonNull String id) { } } + /** + * Starts a UI trace. + * + * @param name string name of the UI trace. + */ @Override public void startUITrace(@NonNull String name) { try { @@ -154,6 +249,9 @@ public void startUITrace(@NonNull String name) { } } + /** + * This method is used to terminate the currently active UI trace. + */ @Override public void endUITrace() { try { @@ -163,6 +261,9 @@ public void endUITrace() { } } + /** + * This method is used to signal the end of the app launch process. + */ @Override public void endAppLaunch() { try { @@ -172,6 +273,12 @@ public void endAppLaunch() { } } + + /** + * logs network-related information + * + * @param data Map of network data object. + */ @Override public void networkLogAndroid(@NonNull Map data) { try { @@ -265,8 +372,13 @@ public void networkLogAndroid(@NonNull Map data) { } - - + /** + * This method is responsible for initiating a custom performance UI trace + * in the APM module. It takes three parameters: + * @param screenName: A string representing the name of the screen or UI element being traced. + * @param microTimeStamp: A number representing the timestamp in microseconds when the trace is started. + * @param traceId: A number representing the unique identifier for the trace. + */ @Override public void startCpUiTrace(@NonNull String screenName, @NonNull Long microTimeStamp, @NonNull Long traceId) { try { @@ -276,6 +388,17 @@ public void startCpUiTrace(@NonNull String screenName, @NonNull Long microTimeSt } } + + /** + * This method is responsible for reporting the screen + * loading data from Dart side to Android side. It takes three parameters: + * @param startTimeStampMicro: A number representing the start timestamp in microseconds of the screen + * loading custom performance data. + * @param durationMicro: A number representing the duration in microseconds of the screen loading custom + * performance data. + * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the + * screen loading. + */ @Override public void reportScreenLoadingCP(@NonNull Long startTimeStampMicro, @NonNull Long durationMicro, @NonNull Long uiTraceId) { try { @@ -285,6 +408,15 @@ public void reportScreenLoadingCP(@NonNull Long startTimeStampMicro, @NonNull Lo } } + + /** + * This method is responsible for extend the end time if the screen loading custom + * trace. It takes two parameters: + * @param timeStampMicro: A number representing the timestamp in microseconds when the screen loading + * custom trace is ending. + * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the + * screen loading. + */ @Override public void endScreenLoadingCP(@NonNull Long timeStampMicro, @NonNull Long uiTraceId) { try { @@ -294,11 +426,16 @@ public void endScreenLoadingCP(@NonNull Long timeStampMicro, @NonNull Long uiTra } } + + /** + * This method is used to check whether the end screen loading feature is enabled or not. + */ @Override public void isEndScreenLoadingEnabled(@NonNull ApmPigeon.Result result) { isScreenLoadingEnabled(result); } + @Override public void isEnabled(@NonNull ApmPigeon.Result result) { try { @@ -310,6 +447,9 @@ public void isEnabled(@NonNull ApmPigeon.Result result) { } } + /** + * checks whether the screen loading feature is enabled. + * */ @Override public void isScreenLoadingEnabled(@NonNull ApmPigeon.Result result) { try { @@ -324,6 +464,9 @@ public void invoke(boolean isFeatureAvailable) { } } + /** + * This method is setting the enabled state of the screen loading feature. + */ @Override public void setScreenLoadingEnabled(@NonNull Boolean isEnabled) { try { diff --git a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java index 7a8549718..edfde055a 100644 --- a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java @@ -13,6 +13,7 @@ import com.instabug.flutter.util.ArgsRegistry; import com.instabug.flutter.util.Reflection; import com.instabug.flutter.util.ThreadManager; +import com.instabug.library.ReproMode; import com.instabug.library.internal.crossplatform.CoreFeature; import com.instabug.library.internal.crossplatform.CoreFeaturesState; import com.instabug.library.internal.crossplatform.FeaturesStateListener; @@ -334,7 +335,7 @@ public void setReproStepsConfig(@Nullable String bugMode, @Nullable String crash if (crashMode != null) { final Integer resolvedCrashMode = ArgsRegistry.reproModes.get(crashMode); - builder.setIssueMode(IssueType.Crash, resolvedCrashMode); + builder.setIssueMode(IssueType.AllCrashes, resolvedCrashMode); } if (sessionReplayMode != null) { @@ -358,6 +359,12 @@ public void reportScreenChange(@NonNull String screenName) { if (method != null) { method.invoke(null, null, screenName); } + Method reportView = Reflection.getMethod(Class.forName("com.instabug.library.Instabug"), "reportCurrentViewChange", + String.class); + + if (reportView != null) { + reportView.invoke(null, screenName); + } } catch (Exception e) { e.printStackTrace(); } @@ -492,4 +499,14 @@ public Map isW3CFeatureFlagsEnabled() { public void willRedirectToStore() { Instabug.willRedirectToStore(); } + + + @Override + public void setNetworkLogBodyEnabled(@NonNull Boolean isEnabled) { + try { + Instabug.setNetworkLogBodyEnabled(isEnabled); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java index 3d0b15ed4..97b9cdf7b 100644 --- a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java +++ b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java @@ -460,7 +460,7 @@ public void testSetReproStepsConfig() { ReproConfigurations.Builder builder = mReproConfigurationsBuilder.constructed().get(0); verify(builder).setIssueMode(IssueType.Bug, ReproMode.EnableWithScreenshots); - verify(builder).setIssueMode(IssueType.Crash, ReproMode.Disable); + verify(builder).setIssueMode(IssueType.AllCrashes, ReproMode.Disable); verify(builder).setIssueMode(IssueType.SessionReplay, ReproMode.Disable); verify(builder).build(); @@ -474,6 +474,7 @@ public void testReportScreenChange() { api.reportScreenChange(screenName); reflected.verify(() -> MockReflected.reportScreenChange(null, screenName)); + reflected.verify(() -> MockReflected.reportCurrentViewChange(screenName)); } @Test @@ -643,4 +644,18 @@ public void isW3CFeatureFlagsEnabled() { assertEquals(isW3cCaughtHeaderEnabled, flags.get("isW3cCaughtHeaderEnabled")); } + + @Test + public void testSetNetworkLogBodyEnabled() { + api.setNetworkLogBodyEnabled(true); + + mInstabug.verify(() -> Instabug.setNetworkLogBodyEnabled(true)); + } + + @Test + public void testSetNetworkLogBodyDisabled() { + api.setNetworkLogBodyEnabled(false); + + mInstabug.verify(() -> Instabug.setNetworkLogBodyEnabled(false)); + } } diff --git a/android/src/test/java/com/instabug/flutter/util/GlobalMocks.java b/android/src/test/java/com/instabug/flutter/util/GlobalMocks.java index 0795acf77..9ab7802ec 100644 --- a/android/src/test/java/com/instabug/flutter/util/GlobalMocks.java +++ b/android/src/test/java/com/instabug/flutter/util/GlobalMocks.java @@ -57,6 +57,13 @@ public static void setUp() throws NoSuchMethodException { Bitmap.class, String.class)) .thenReturn(mReportScreenChange); + Method mReportCurrentViewChange= MockReflected.class.getDeclaredMethod("reportCurrentViewChange", String.class); + mReportCurrentViewChange.setAccessible(true); + reflection + .when(() -> Reflection.getMethod(Class.forName("com.instabug.library.Instabug"), "reportCurrentViewChange", + String.class)) + .thenReturn(mReportCurrentViewChange); + Method mSetCustomBrandingImage = MockReflected.class.getDeclaredMethod("setCustomBrandingImage", Bitmap.class, Bitmap.class); mSetCustomBrandingImage.setAccessible(true); reflection diff --git a/android/src/test/java/com/instabug/flutter/util/MockReflected.java b/android/src/test/java/com/instabug/flutter/util/MockReflected.java index 42c85664b..9bf03cc53 100644 --- a/android/src/test/java/com/instabug/flutter/util/MockReflected.java +++ b/android/src/test/java/com/instabug/flutter/util/MockReflected.java @@ -21,6 +21,11 @@ public class MockReflected { */ public static void reportScreenChange(Bitmap screenshot, String name) {} + /** + * Instabug.reportCurrentViewChange + */ + public static void reportCurrentViewChange(String name) {} + /** * Instabug.setCustomBrandingImage */ diff --git a/e2e/BugReportingTests.cs b/e2e/BugReportingTests.cs index f938cfcbd..f48afbf3b 100644 --- a/e2e/BugReportingTests.cs +++ b/e2e/BugReportingTests.cs @@ -1,9 +1,9 @@ -using System.Drawing; using E2E.Utils; using Xunit; using Instabug.Captain; using NoSuchElementException = OpenQA.Selenium.NoSuchElementException; +using System.Drawing; namespace E2E; @@ -46,6 +46,9 @@ public void ReportABug() [Fact] public void FloatingButtonInvocationEvent() { + + Console.WriteLine("FloatingButtonInvocationEvent"); + captain.FindById( android: "instabug_floating_button", ios: "IBGFloatingButtonAccessibilityIdentifier" @@ -57,9 +60,13 @@ public void FloatingButtonInvocationEvent() [Fact] public void ShakeInvocationEvent() { + + Console.WriteLine("ShakeInvocationEvent"); + if (!Platform.IsIOS) return; - captain.FindByText("Shake").Tap(); + + captain.FindByTextScroll("Shake").Tap(); captain.Shake(); @@ -69,8 +76,12 @@ public void ShakeInvocationEvent() [Fact] public void TwoFingersSwipeLeftInvocationEvent() { - ScrollUp(); - captain.FindByText("Two Fingers Swipe Left").Tap(); + + Console.WriteLine("TwoFingersSwipeLeftInvocationEvent"); + + + + captain.FindByTextScroll("Two Fingers Swipe Left").Tap(); Thread.Sleep(500); @@ -89,7 +100,11 @@ public void TwoFingersSwipeLeftInvocationEvent() [Fact] public void NoneInvocationEvent() { - captain.FindByText("None").Tap(); + + Console.WriteLine("NoneInvocationEvent"); + + + captain.FindByTextScroll("None").Tap(); captain.WaitForAssertion(() => Assert.Throws(() => @@ -105,7 +120,13 @@ public void NoneInvocationEvent() [Fact] public void ManualInvocation() { - captain.FindByText("Invoke").Tap(); + + + Console.WriteLine("ManualInvocation"); + + + + captain.FindByTextScroll("Invoke").Tap(); AssertOptionsPromptIsDisplayed(); } @@ -113,14 +134,24 @@ public void ManualInvocation() [Fact] public void MultipleScreenshotsInReproSteps() { - ScrollDownLittle(); - captain.FindByText("Enter screen name").Tap(); + + Console.WriteLine("MultipleScreenshotsInReproSteps"); + + + + +captain.FindByTextScroll("Enter screen name").Tap(); captain.Type("My Screen"); captain.HideKeyboard(); - captain.FindByText("Report Screen Change").Tap(); - captain.FindByText("Send Bug Report").Tap(); + captain.HideKeyboard(); + Thread.Sleep(500); + + captain.FindByTextScroll("Report Screen Change")?.Tap(); + Thread.Sleep(500); + captain.FindByTextScroll("Send Bug Report")?.Tap(); + captain.FindById( android: "instabug_text_view_repro_steps_disclaimer", ios: "IBGBugVCReproStepsDisclaimerAccessibilityIdentifier" @@ -136,27 +167,30 @@ public void MultipleScreenshotsInReproSteps() [Fact(Skip = "The test is flaky on iOS so we're skipping it to unblock the v13.2.0 release")] public void ChangeReportTypes() { - ScrollUp(); - captain.FindByText("Bug", exact: true).Tap(); + + Console.WriteLine("ChangeReportTypes"); + + + captain.FindByTextScroll("Bug", exact: true).Tap(); if (Platform.IsAndroid) { - captain.FindByText("Invoke").Tap(); + captain.FindByTextScroll("Invoke").Tap(); // Shows bug reporting screen Assert.True(captain.FindById("ib_bug_scroll_view").Displayed); // Close bug reporting screen captain.GoBack(); - captain.FindByText("DISCARD").Tap(); + captain.FindByTextScroll("DISCARD").Tap(); Thread.Sleep(500); } - captain.FindByText("Feedback").Tap(); + captain.FindByTextScroll("Feedback").Tap(); - captain.FindByText("Invoke").Tap(); + captain.FindByTextScroll("Invoke").Tap(); // Shows both bug reporting and feature requests in prompt options AssertOptionsPromptIsDisplayed(); @@ -169,10 +203,12 @@ public void ChangeReportTypes() [Fact] public void ChangeFloatingButtonEdge() { - ScrollDown(); - captain.FindByText("Move Floating Button to Left").Tap(); - Thread.Sleep(500); + Console.WriteLine("ChangeFloatingButtonEdge"); + + + captain.FindByTextScroll("Move Floating Button to Left",false,false)?.Tap(); + captain.WaitForAssertion(() => { @@ -189,16 +225,16 @@ public void ChangeFloatingButtonEdge() [Fact] public void OnDismissCallbackIsCalled() { - ScrollDownLittle(); - captain.FindByText("Set On Dismiss Callback").Tap(); - captain.FindByText("Invoke").Tap(); + captain.FindByTextScroll("Set On Dismiss Callback",false,false).Tap(); + captain.FindByTextScroll("Invoke",false,false).Tap(); AssertOptionsPromptIsDisplayed(); - captain.FindByText("Cancel").Tap(); + captain.FindByTextScroll("Cancel").Tap(); var popUpText = captain.FindByText("onDismiss callback called with DismissType.cancel and ReportType.other"); Assert.True(popUpText.Displayed); + } } diff --git a/e2e/E2E.csproj b/e2e/E2E.csproj index 7e6759754..f54272903 100644 --- a/e2e/E2E.csproj +++ b/e2e/E2E.csproj @@ -1,23 +1,23 @@ - - - - net6.0 - enable - enable - - false - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - + + + + net6.0 + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/e2e/E2E.sln b/e2e/E2E.sln index 23c0ba9eb..c2e05e1e2 100644 --- a/e2e/E2E.sln +++ b/e2e/E2E.sln @@ -1,31 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 25.0.1703.5 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E2E", "E2E.csproj", "{99118060-0344-4CAD-AE3A-CC74A5E0F3C7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Instabug.Captain", "..\..\Instabug.Captain\Instabug.Captain\Instabug.Captain.csproj", "{597F3BAD-11D3-43B7-A95C-6C41CD2096A8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Release|Any CPU.Build.0 = Release|Any CPU - {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3E37F932-775C-4756-A8FF-28DFC6CAC624} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1703.5 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E2E", "E2E.csproj", "{99118060-0344-4CAD-AE3A-CC74A5E0F3C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Instabug.Captain", "..\..\Instabug.Captain\Instabug.Captain\Instabug.Captain.csproj", "{597F3BAD-11D3-43B7-A95C-6C41CD2096A8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99118060-0344-4CAD-AE3A-CC74A5E0F3C7}.Release|Any CPU.Build.0 = Release|Any CPU + {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {597F3BAD-11D3-43B7-A95C-6C41CD2096A8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3E37F932-775C-4756-A8FF-28DFC6CAC624} + EndGlobalSection +EndGlobal diff --git a/e2e/FeatureRequestsTests.cs b/e2e/FeatureRequestsTests.cs index 41c97f684..724758bee 100644 --- a/e2e/FeatureRequestsTests.cs +++ b/e2e/FeatureRequestsTests.cs @@ -10,10 +10,10 @@ public class FeatureRequestsTests : CaptainTest [Fact] public void ShowFeatureRequetsScreen() { - ScrollDown(); - ScrollDown(); - captain.FindByText("Show Feature Requests").Tap(); + Console.WriteLine("ShowFeatureRequetsScreen"); + + captain.FindByTextScroll("Show Feature Requests",false,false).Tap(); var screenTitle = captain.FindById( android: "ib_fr_toolbar_main", diff --git a/e2e/InstabugTests.cs b/e2e/InstabugTests.cs index 1b67ae8cd..e376f87bf 100644 --- a/e2e/InstabugTests.cs +++ b/e2e/InstabugTests.cs @@ -11,14 +11,16 @@ public class InstabugTests : CaptainTest [Fact] public void ChangePrimaryColor() { + Console.WriteLine("ChangePrimaryColor"); + var color = "#FF0000"; var expected = Color.FromArgb(255, 0, 0); - captain.FindByText("Enter primary color").Tap(); + captain.FindByTextScroll("Enter primary color").Tap(); captain.Type(color); captain.HideKeyboard(); - captain.FindByText("Change Primary Color").Tap(); + captain.FindByTextScroll("Change Primary Color").Tap(); captain.WaitForAssertion(() => { diff --git a/e2e/SurveysTests.cs b/e2e/SurveysTests.cs index 1ed0eba48..0d67f99ab 100644 --- a/e2e/SurveysTests.cs +++ b/e2e/SurveysTests.cs @@ -10,8 +10,9 @@ public class SurveysTests : CaptainTest [Fact] public void ShowManualSurvey() { - ScrollDownLittle(); - captain.FindByText("Show Manual Survey").Tap(); + Console.WriteLine("ShowManualSurvey"); + + captain.FindByTextScroll("Show Manual Survey",false,false).Tap(); captain.WaitForAssertion(() => { diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 698a32c86..8e777d514 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,53 +1,30 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 34 - ndkVersion flutter.ndkVersion + namespace = "com.instabug.flutter.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.instabug.flutter.example" minSdkVersion 21 targetSdkVersion 34 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + versionCode flutter.versionCode + versionName flutter.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true } @@ -56,7 +33,7 @@ android { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } namespace 'com.instabug.flutter.example' @@ -71,7 +48,7 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.20" implementation 'com.android.support:multidex:1.0.3' implementation 'org.mockito:mockito-core:1.10.19' testImplementation 'junit:junit:4.12' diff --git a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt index 1c51ac902..17a7d35c6 100644 --- a/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt +++ b/example/android/app/src/main/kotlin/com/example/InstabugSample/InstabugExampleMethodCallHandler.kt @@ -1,4 +1,6 @@ package com.example.InstabugSample +import android.os.Handler +import android.os.Looper import android.util.Log import com.instabug.crash.CrashReporting import com.instabug.crash.models.IBGNonFatalException @@ -62,7 +64,10 @@ class InstabugExampleMethodCallHandler : MethodChannel.MethodCallHandler { } private fun sendNativeFatalCrash() { - throw IllegalStateException("Unhandled IllegalStateException from Instabug Test App") + Handler(Looper.getMainLooper()).post { + throw IllegalStateException("Unhandled IllegalStateException from Instabug Test App") + + } } private fun sendANR() { diff --git a/example/android/build.gradle b/example/android/build.gradle index 3a64d7e1e..ed40a47e2 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,23 +1,3 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.0.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - rootProject.buildDir = '../build' //android { diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 89e56bdb6..4cf0c849a 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 8f6a080fc..32cfc43dc 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,12 +1,27 @@ -rootProject.name = 'InstabugExample' -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url 'https://storage.googleapis.com/flutter-plugins' } -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/example/ios/InstabugTests/InstabugApiTests.m b/example/ios/InstabugTests/InstabugApiTests.m index 6c9544d71..943e030dd 100644 --- a/example/ios/InstabugTests/InstabugApiTests.m +++ b/example/ios/InstabugTests/InstabugApiTests.m @@ -325,7 +325,7 @@ - (void)testSetReproStepsConfig { [self.api setReproStepsConfigBugMode:bugMode crashMode:crashMode sessionReplayMode:sessionReplayMode error:&error]; OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeBug withMode:IBGUserStepsModeEnable]); - OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeCrash withMode:IBGUserStepsModeDisable]); + OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeAllCrashes withMode:IBGUserStepsModeDisable]); OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeSessionReplay withMode:IBGUserStepsModeDisable]); } @@ -451,6 +451,15 @@ - (void)testWillRedirectToAppStore { OCMVerify([self.mInstabug willRedirectToAppStore]); } +- (void)testSetNetworkLogBodyEnabled { + NSNumber *isEnabled = @1; + FlutterError *error; + + [self.api setNetworkLogBodyEnabledIsEnabled:isEnabled error:&error]; + + XCTAssertTrue(IBGNetworkLogger.logBodyEnabled); +} + - (void)testNetworkLogWithW3Caught { NSString *url = @"https://example.com"; NSString *requestBody = @"hi"; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 8072dd94f..484d0ae99 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Instabug (14.1.0) - - instabug_flutter (14.0.0): + - Instabug (14.3.0) + - instabug_flutter (14.3.0): - Flutter - - Instabug (= 14.1.0) + - Instabug (= 14.3.0) - OCMock (3.6) DEPENDENCIES: @@ -24,8 +24,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - Instabug: 8cbca8974168c815658133e2813f5ac3a36f8e20 - instabug_flutter: a24751dfaedd29475da2af062d3e19d697438f72 + Instabug: 97a4e694731f46bbc02dbe49ab29cc552c5e2f41 + instabug_flutter: 4e4a9b162d77d5624d08ccdf81745d923745e062 OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 PODFILE CHECKSUM: 8f7552fd115ace1988c3db54a69e4a123c448f84 diff --git a/example/lib/main.dart b/example/lib/main.dart index cda7ff3ec..91b0a67e7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter_example/src/components/apm_switch.dart'; import 'package:instabug_http_client/instabug_http_client.dart'; import 'package:instabug_flutter_example/src/app_routes.dart'; import 'package:instabug_flutter_example/src/widget/nested_view.dart'; diff --git a/example/lib/src/components/apm_switch.dart b/example/lib/src/components/apm_switch.dart new file mode 100644 index 000000000..df8dd6123 --- /dev/null +++ b/example/lib/src/components/apm_switch.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter_example/src/utils/show_messages.dart'; + +class APMSwitch extends StatefulWidget { + const APMSwitch({Key? key}) : super(key: key); + + @override + State createState() => _APMSwitchState(); +} + +class _APMSwitchState extends State { + bool isEnabled = false; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SwitchListTile.adaptive( + title: const Text('APM Enabled'), + value: isEnabled, + onChanged: (value) => onAPMChanged(context, value), + ), + ], + ); + } + + void onAPMChanged(BuildContext context, bool value) { + APM.setEnabled(value); + showSnackBar(context, "APM is ${value ? "enabled" : "disabled"}"); + setState(() => isEnabled = value); + } +} diff --git a/example/lib/src/components/network_content.dart b/example/lib/src/components/network_content.dart index 77ac744b2..23eaa4dd2 100644 --- a/example/lib/src/components/network_content.dart +++ b/example/lib/src/components/network_content.dart @@ -26,7 +26,7 @@ class _NetworkContentState extends State { text: 'Send Request To Url', onPressed: () => _sendRequestToUrl(endpointUrlController.text), ), - Text("W3C Header Section"), + const Text("W3C Header Section"), InstabugButton( text: 'Send Request With Custom traceparent header', onPressed: () => _sendRequestToUrl(endpointUrlController.text, diff --git a/example/lib/src/screens/apm_page.dart b/example/lib/src/screens/apm_page.dart index 8580e203f..798e906fa 100644 --- a/example/lib/src/screens/apm_page.dart +++ b/example/lib/src/screens/apm_page.dart @@ -22,11 +22,18 @@ class _ApmPageState extends State { ); } + _endAppLaunch() => APM.endAppLaunch(); + @override Widget build(BuildContext context) { return Page( title: 'APM', children: [ + const APMSwitch(), + InstabugButton( + text: 'End App Launch', + onPressed: _endAppLaunch, + ), const SectionTitle('Network'), const NetworkContent(), const SectionTitle('Traces'), diff --git a/example/lib/src/utils/show_messages.dart b/example/lib/src/utils/show_messages.dart new file mode 100644 index 000000000..2bb62b05f --- /dev/null +++ b/example/lib/src/utils/show_messages.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +void showSnackBar(BuildContext context, String message) { + final messenger = ScaffoldMessenger.of(context); + messenger.clearSnackBars(); + messenger.showSnackBar( + SnackBar( + content: Text(message), + ), + ); +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6550e5302..93971dc8b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -107,7 +107,7 @@ packages: path: ".." relative: true source: path - version: "14.1.0" + version: "14.3.0" instabug_http_client: dependency: "direct main" description: diff --git a/ios/Classes/Modules/ApmApi.m b/ios/Classes/Modules/ApmApi.m index 83efce69d..782765ad7 100644 --- a/ios/Classes/Modules/ApmApi.m +++ b/ios/Classes/Modules/ApmApi.m @@ -19,10 +19,16 @@ - (instancetype)init { return self; } +// This method is setting the enabled state of the APM feature. It +// takes a boolean value wrapped in an NSNumber object as a parameter and sets the APM enabled state +// based on that value. The `IBGAPM.enabled` property is being set to the boolean value extracted from +// the NSNumber parameter using the `boolValue` method. - (void)setEnabledIsEnabled:(NSNumber *)isEnabled error:(FlutterError *_Nullable *_Nonnull)error { IBGAPM.enabled = [isEnabled boolValue]; } +// This method is used to check if the APM feature is enabled. +// `completion` handler is a way for implementing callback functionality. - (void)isEnabledWithCompletion:(nonnull void (^)(NSNumber * _Nullable, FlutterError * _Nullable))completion { BOOL isEnabled = IBGAPM.enabled; @@ -31,25 +37,43 @@ - (void)isEnabledWithCompletion:(nonnull void (^)(NSNumber * _Nullable, FlutterE completion(isEnabledNumber, nil); } +// This method is setting the enabled state of the screen loading feature in the APM module. +// It takes a boolean value wrapped in an NSNumber object as a parameter. +//The method then extracts the boolean value from the NSNumber parameter using the +// `boolValue` method and sets the screen loading enabled state in the APM module based on that value. - (void)setScreenLoadingEnabledIsEnabled:(nonnull NSNumber *)isEnabled error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { [IBGAPM setScreenLoadingEnabled:[isEnabled boolValue]]; } +// checks whether the screen loading feature is enabled. +//`completion` handler is a way for implementing callback functionality. - (void)isScreenLoadingEnabledWithCompletion:(nonnull void (^)(NSNumber * _Nullable, FlutterError * _Nullable))completion { BOOL isScreenLoadingEnabled = IBGAPM.screenLoadingEnabled; NSNumber *isEnabledNumber = @(isScreenLoadingEnabled); completion(isEnabledNumber, nil); } +// This method is setting the enabled state of the cold app launch feature in the APM module. It takes +// a boolean value wrapped in an NSNumber object as a parameter. The method then extracts the boolean +// value from the NSNumber parameter using the `boolValue` method and sets the cold app launch enabled +// state in the APM module based on that value. - (void)setColdAppLaunchEnabledIsEnabled:(NSNumber *)isEnabled error:(FlutterError *_Nullable *_Nonnull)error { IBGAPM.coldAppLaunchEnabled = [isEnabled boolValue]; } +// This method is setting the enabled state of the auto UI trace feature in the APM module. It takes a +// boolean value wrapped in an NSNumber object as a parameter. The method then extracts the boolean +// value from the NSNumber parameter using the `boolValue` method and sets the auto UI trace enabled +// state in the APM module based on that value. - (void)setAutoUITraceEnabledIsEnabled:(NSNumber *)isEnabled error:(FlutterError *_Nullable *_Nonnull)error { IBGAPM.autoUITraceEnabled = [isEnabled boolValue]; } +// This method is responsible for starting an execution trace +// with a given `id` and `name`. +// +// Deprecated - see [startFlowName, setFlowAttributeName & endFlowName]. - (void)startExecutionTraceId:(NSString *)id name:(NSString *)name completion:(void(^)(NSString *_Nullable, FlutterError *_Nullable))completion { IBGExecutionTrace *trace = [IBGAPM startExecutionTraceWithName:name]; @@ -61,6 +85,10 @@ - (void)startExecutionTraceId:(NSString *)id name:(NSString *)name completion:(v } } +// This method is responsible for setting an attribute for a specific +// execution trace identified by the provided `id`. +// +// Deprecated - see [startFlowName, setFlowAttributeName & endFlowName]. - (void)setExecutionTraceAttributeId:(NSString *)id key:(NSString *)key value:(NSString *)value error:(FlutterError *_Nullable *_Nonnull)error { IBGExecutionTrace *trace = [traces objectForKey:id]; @@ -69,6 +97,10 @@ - (void)setExecutionTraceAttributeId:(NSString *)id key:(NSString *)key value:(N } } +// This method `endExecutionTraceId` is responsible for ending an execution trace identified by the +// provided `id`. +// +// Deprecated - see [startFlowName, setFlowAttributeName & endFlowName]. - (void)endExecutionTraceId:(NSString *)id error:(FlutterError *_Nullable *_Nonnull)error { IBGExecutionTrace *trace = [traces objectForKey:id]; @@ -77,26 +109,40 @@ - (void)endExecutionTraceId:(NSString *)id error:(FlutterError *_Nullable *_Nonn } } +// This method is responsible for starting a flow with the given `name`. This functionality is used to +// track and monitor the performance of specific flows within the application. - (void)startFlowName:(nonnull NSString *)name error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { [IBGAPM startFlowWithName:name]; } +// This method sets an attribute for a specific flow identified by the +// provided `name`. It takes three parameters: +// 1. `name`: The name of the flow for which the attribute needs to be set. +// 2. `key`: The key of the attribute being set. +// 3. `value`: The value of the attribute being set. - (void)setFlowAttributeName:(nonnull NSString *)name key:(nonnull NSString *)key value:(nullable NSString *)value error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { [IBGAPM setAttributeForFlowWithName:name key:key value:value]; } +// This method is responsible for ending a flow with the given `name`. +// This functionality helps in monitoring and tracking the performance of different flows within the application. - (void)endFlowName:(nonnull NSString *)name error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { [IBGAPM endFlowWithName:name]; } +// This method is responsible for starting a UI trace with the given `name`. +// Which initiates the tracking of user interface interactions for monitoring the performance of the application. - (void)startUITraceName:(NSString *)name error:(FlutterError *_Nullable *_Nonnull)error { [IBGAPM startUITraceWithName:name]; } +// The method is responsible for ending the currently active UI trace. +// Which signifies the completion of tracking user interface interactions. - (void)endUITraceWithError:(FlutterError *_Nullable *_Nonnull)error { [IBGAPM endUITrace]; } +// The method is responsible for ending the app launch process in the APM module. - (void)endAppLaunchWithError:(FlutterError *_Nullable *_Nonnull)error { [IBGAPM endAppLaunch]; } @@ -106,6 +152,11 @@ - (void)networkLogAndroidData:(NSDictionary *)data error:(Flutte } +// This method is responsible for initiating a custom performance UI trace +// in the APM module. It takes three parameters: +// 1. `screenName`: A string representing the name of the screen or UI element being traced. +// 2. `microTimeStamp`: A number representing the timestamp in microseconds when the trace is started. +// 3. `traceId`: A number representing the unique identifier for the trace. - (void)startCpUiTraceScreenName:(nonnull NSString *)screenName microTimeStamp:(nonnull NSNumber *)microTimeStamp traceId:(nonnull NSNumber *)traceId error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { NSTimeInterval startTimeStampMUS = [microTimeStamp doubleValue]; [IBGAPM startUITraceCPWithName:screenName startTimestampMUS:startTimeStampMUS]; @@ -113,17 +164,33 @@ - (void)startCpUiTraceScreenName:(nonnull NSString *)screenName microTimeStamp:( +// This method is responsible for reporting the screen +// loading data from Dart side to iOS side. It takes three parameters: +// 1. `startTimeStampMicro`: A number representing the start timestamp in microseconds of the screen +// loading custom performance data. +// 2. `durationMicro`: A number representing the duration in microseconds of the screen loading custom +// performance data. +// 3. `uiTraceId`: A number representing the unique identifier for the UI trace associated with the +// screen loading. - (void)reportScreenLoadingCPStartTimeStampMicro:(nonnull NSNumber *)startTimeStampMicro durationMicro:(nonnull NSNumber *)durationMicro uiTraceId:(nonnull NSNumber *)uiTraceId error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { NSTimeInterval startTimeStampMicroMUS = [startTimeStampMicro doubleValue]; NSTimeInterval durationMUS = [durationMicro doubleValue]; [IBGAPM reportScreenLoadingCPWithStartTimestampMUS:startTimeStampMicroMUS durationMUS:durationMUS]; } +// This method is responsible for extend the end time if the screen loading custom +// trace. It takes two parameters: +// 1. `timeStampMicro`: A number representing the timestamp in microseconds when the screen loading +// custom trace is ending. +// 2. `uiTraceId`: A number representing the unique identifier for the UI trace associated with the +// screen loading. - (void)endScreenLoadingCPTimeStampMicro:(nonnull NSNumber *)timeStampMicro uiTraceId:(nonnull NSNumber *)uiTraceId error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { NSTimeInterval endScreenLoadingCPWithEndTimestampMUS = [timeStampMicro doubleValue]; [IBGAPM endScreenLoadingCPWithEndTimestampMUS:endScreenLoadingCPWithEndTimestampMUS]; } +// This method is used to check whether the end screen loading feature is enabled or not. +//`completion` handler is a way for implementing callback functionality. - (void)isEndScreenLoadingEnabledWithCompletion:(nonnull void (^)(NSNumber * _Nullable, FlutterError * _Nullable))completion { BOOL isEndScreenLoadingEnabled = IBGAPM.endScreenLoadingEnabled; NSNumber *isEnabledNumber = @(isEndScreenLoadingEnabled); diff --git a/ios/Classes/Modules/InstabugApi.m b/ios/Classes/Modules/InstabugApi.m index 8cdd336d1..95222a4c8 100644 --- a/ios/Classes/Modules/InstabugApi.m +++ b/ios/Classes/Modules/InstabugApi.m @@ -163,7 +163,7 @@ - (void)setReproStepsConfigBugMode:(nullable NSString *)bugMode crashMode:(nulla if (crashMode != nil) { IBGUserStepsMode resolvedCrashMode = ArgsRegistry.reproModes[crashMode].integerValue; - [Instabug setReproStepsFor:IBGIssueTypeCrash withMode:resolvedCrashMode]; + [Instabug setReproStepsFor:IBGIssueTypeAllCrashes withMode:resolvedCrashMode]; } if (sessionReplayMode != nil) { @@ -391,6 +391,9 @@ - (void)registerFeatureFlagChangeListenerWithError:(FlutterError * _Nullable __a }; return result; } - +- (void)setNetworkLogBodyEnabledIsEnabled:(NSNumber *)isEnabled + error:(FlutterError *_Nullable *_Nonnull)error { + IBGNetworkLogger.logBodyEnabled = [isEnabled boolValue]; +} @end diff --git a/ios/instabug_flutter.podspec b/ios/instabug_flutter.podspec index f339d7fe0..deb154e51 100644 --- a/ios/instabug_flutter.podspec +++ b/ios/instabug_flutter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'instabug_flutter' - s.version = '14.0.0' + s.version = '14.3.0' s.summary = 'Flutter plugin for integrating the Instabug SDK.' s.author = 'Instabug' s.homepage = 'https://www.instabug.com/platforms/flutter' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-framework "Flutter" -framework "Instabug"'} s.dependency 'Flutter' - s.dependency 'Instabug', '14.1.0' + s.dependency 'Instabug', '14.3.0' end diff --git a/lib/src/modules/apm.dart b/lib/src/modules/apm.dart index 70f7bf6b0..a9f6e0a7c 100644 --- a/lib/src/modules/apm.dart +++ b/lib/src/modules/apm.dart @@ -29,26 +29,45 @@ class APM { return _host.setEnabled(isEnabled); } - /// @nodoc + /// Returns a Future indicating whether [APM] is enabled. + /// + /// Returns: + /// A Future object is being returned. @internal static Future isEnabled() async { return _host.isEnabled(); } - /// Enables or disables the screenLoading Monitoring feature. - /// [boolean] isEnabled + /// Sets the screen loading state based on the provided boolean value. + /// + /// Args: + /// isEnabled (bool): The [isEnabled] parameter is a boolean value that determines whether screen + /// loading is enabled or disabled. If [isEnabled] is `true`, screen loading will be enabled; if + /// [isEnabled] is `false`, screen loading will be disabled. + /// + /// Returns: + /// A Future is being returned. static Future setScreenLoadingEnabled(bool isEnabled) { return _host.setScreenLoadingEnabled(isEnabled); } - /// @nodoc + /// Returns a Future indicating whether screen loading is enabled. + /// + /// Returns: + /// A Future object is being returned. @internal static Future isScreenLoadingEnabled() async { return _host.isScreenLoadingEnabled(); } - /// Enables or disables cold app launch tracking. - /// [boolean] isEnabled + /// Sets whether cold app launch is enabled or not. + /// + /// Args: + /// isEnabled (bool): The [setColdAppLaunchEnabled] method takes a boolean parameter [isEnabled] which + /// indicates whether cold app launch is enabled or not. + /// + /// Returns: + /// The method is returning a `Future`. static Future setColdAppLaunchEnabled(bool isEnabled) async { return _host.setColdAppLaunchEnabled(isEnabled); } @@ -136,40 +155,92 @@ class APM { return _host.setFlowAttribute(name, key, value); } - /// Ends the AppFlow with the given [name]. + /// Ends a flow with the specified name. + /// + /// Args: + /// name (String): The [name] parameter is a required string that represents the name of the flow that you want + /// to end. + /// + /// Returns: + /// The method is returning a `Future`. static Future endFlow(String name) async { return _host.endFlow(name); } - /// Enables or disables auto UI tracing. - /// [boolean] isEnabled + /// Sets whether auto UI trace is enabled or not. + /// + /// Args: + /// isEnabled (bool): The [isEnabled] parameter is a boolean value that determines whether the auto + /// UI trace feature should be enabled or disabled. If [isEnabled] is set to `true`, the auto UI trace + /// feature will be enabled, and if it is set to `false`, the feature will be disabled. + /// + /// Returns: + /// The method returns a `Future`. static Future setAutoUITraceEnabled(bool isEnabled) async { return _host.setAutoUITraceEnabled(isEnabled); } - /// Starts UI trace. - /// [String] name + /// Starts a UI trace with the given name. + /// + /// Args: + /// name (String): The [name] parameter in the [startUITrace] method is a string that represents the + /// name of the UI trace that will be started. + /// + /// Returns: + /// The method is returning a `Future`. static Future startUITrace(String name) async { return _host.startUITrace(name); } - /// Ends UI trace. + /// The [endUITrace] function ends a UI trace. + /// + /// Returns: + /// The method is returning a `Future`. static Future endUITrace() async { return _host.endUITrace(); } - /// Ends App Launch. + /// Defines when an app launch is complete, + /// such as when it's intractable, use the end app launch API. + /// You can then view this data with the automatic cold and hot app launches. + /// + /// Returns: + /// The method is returning a `Future`. static Future endAppLaunch() async { return _host.endAppLaunch(); } + /// Logs network data for Instabug Android SDK if the app is running on an + /// Android platform. + /// + /// Args: + /// data (NetworkData): The [data] parameter in the `networkLogAndroid` function is of type + /// [NetworkData] and represents the network data that need to be logged. + /// + /// Returns: + /// The method is returning a `FutureOr`. static FutureOr networkLogAndroid(NetworkData data) { if (IBGBuildInfo.instance.isAndroid) { return _host.networkLogAndroid(data.toJson()); } } - /// @nodoc + /// Logs a message and then starts a UI trace with the provided screen + /// name, start time, and trace ID. + /// + /// Args: + /// screenName (String): The [screenName] parameter represents the identifier of the screen or + /// page where the UI trace is being started. It helps in identifying and tracking the performance of + /// that specific screen within the application. + /// startTimeInMicroseconds (int): The [startTimeInMicroseconds] parameter represents the time at + /// which the UI trace is starting, measured in microseconds. It is used to track the performance and + /// behavior of the user interface during a specific period of time. + /// traceId (int): The [traceId] parameter is used to uniquely identify a particular trace or + /// performance measurement within the system. It helps in tracking and distinguishing different traces + /// for analysis and debugging purposes. + /// + /// Returns: + /// The `startCpUiTrace` method is returning a `Future`. @internal static Future startCpUiTrace( String screenName, @@ -183,7 +254,21 @@ class APM { return _host.startCpUiTrace(screenName, startTimeInMicroseconds, traceId); } - /// @nodoc + /// Reports screen loading trace with specific details. + /// + /// Args: + /// startTimeInMicroseconds (int): The [startTimeInMicroseconds] parameter represents the time when + /// the screen loading operation started, measured in microseconds. + /// durationInMicroseconds (int): The [durationInMicroseconds] parameter represents the duration of + /// the screen loading process in microseconds. It indicates the time taken for the screen to load + /// completely from the start time specified by [startTimeInMicroseconds]. This parameter helps in + /// measuring and analyzing the performance of the screen loading operation. + /// uiTraceId (int): The [uiTraceId] parameter is used to + /// identify a specific trace related to the screen loading process. It helps in tracking + /// and monitoring the performance of the screen loading operation. + /// + /// Returns: + /// The method is returning a `Future`. @internal static Future reportScreenLoadingCP( int startTimeInMicroseconds, @@ -201,7 +286,17 @@ class APM { ); } - /// @nodoc + /// Extends a screen loading trace with the provided end time and UI trace ID. + /// + /// Args: + /// endTimeInMicroseconds (int): The [endTimeInMicroseconds] parameter represents the time at which + /// the screen loading ends, measured in microseconds. + /// uiTraceId (int): The [uiTraceId] parameter is an identifier for the user interface trace being + /// monitored or tracked. It helps in associating the end time of the screen loading with the specific + /// trace for performance monitoring and analysis. + /// + /// Returns: + /// The method is returning a `Future`. @internal static Future endScreenLoadingCP( int endTimeInMicroseconds, @@ -214,19 +309,37 @@ class APM { return _host.endScreenLoadingCP(endTimeInMicroseconds, uiTraceId); } - /// Extends the currently active screen loading trace + /// Extends the currently active screen loading trace using the [ScreenLoadingManager]. + /// + /// Returns: + /// A Future is being returned. static Future endScreenLoading() { return ScreenLoadingManager.I.endScreenLoading(); } - /// @nodoc + /// Returns a Future indicating whether the end screen + /// loading is enabled. + /// + /// Returns: + /// A Future is being returned. @internal static Future isEndScreenLoadingEnabled() async { return _host.isEndScreenLoadingEnabled(); } - /// Wraps the given routes with [InstabugCaptureScreenLoading] widgets. - /// This allows Instabug to automatically capture screen loading times. + /// The function [wrapRoutes] wraps the given routes with a [InstabugCaptureScreenLoading] widget, excluding specified + /// routes. This allows Instabug to automatically capture screen loading times. + /// + /// Args: + /// routes (Map): The [routes] parameter is a map that contains route names as + /// keys and corresponding [WidgetBuilder] functions as values. This map is used to define the available + /// routes in a Flutter application. + /// exclude (List): The [exclude] parameter is a list of route names that you want to exclude + /// from the wrapping process. When the [wrapRoutes] function is called, it will wrap all routes except + /// the ones specified in the [exclude] list. Defaults to const [] + /// + /// Returns: + /// A Map that contains the new wrapped routes. static Map wrapRoutes( Map routes, { List exclude = const [], diff --git a/lib/src/modules/instabug.dart b/lib/src/modules/instabug.dart index 766067df6..6bba8ed1f 100644 --- a/lib/src/modules/instabug.dart +++ b/lib/src/modules/instabug.dart @@ -449,9 +449,9 @@ class Instabug { } return _host.setReproStepsConfig( - bugMode.toString(), - crashMode.toString(), - sessionReplayMode.toString(), + bugMode?.toString(), + crashMode?.toString(), + sessionReplayMode?.toString(), ); } diff --git a/lib/src/modules/network_logger.dart b/lib/src/modules/network_logger.dart index 14e524f87..b7acc74c2 100644 --- a/lib/src/modules/network_logger.dart +++ b/lib/src/modules/network_logger.dart @@ -126,4 +126,10 @@ class NetworkLogger { } return null; } + + /// Enables or disables network body logs capturing. + /// [boolean] isEnabled + static Future setNetworkLogBodyEnabled(bool isEnabled) async { + return _host.setNetworkLogBodyEnabled(isEnabled); + } } diff --git a/lib/src/utils/screen_loading/instabug_capture_screen_loading.dart b/lib/src/utils/screen_loading/instabug_capture_screen_loading.dart index 3d400ae3d..9c9559af4 100644 --- a/lib/src/utils/screen_loading/instabug_capture_screen_loading.dart +++ b/lib/src/utils/screen_loading/instabug_capture_screen_loading.dart @@ -4,15 +4,37 @@ import 'package:instabug_flutter/src/utils/instabug_montonic_clock.dart'; import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_manager.dart'; import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_trace.dart'; +/// A widget that tracks and reports screen loading times to Instabug. +/// +/// This widget wraps around a child widget and measures the time taken +/// for the screen to fully render. The recorded loading time is reported +/// using the [ScreenLoadingManager]. +/// +/// ## Usage +/// ```dart +/// InstabugCaptureScreenLoading( +/// screenName: "HomeScreen", +/// child: HomeScreenWidget(), +/// ) +/// ``` class InstabugCaptureScreenLoading extends StatefulWidget { + /// A unique identifier for the widget used internally for debugging purposes. static const tag = "InstabugCaptureScreenLoading"; + /// Creates an instance of [InstabugCaptureScreenLoading]. + /// + /// Requires [screenName] to identify the screen being tracked and [child] + /// which represents the UI component to be rendered. const InstabugCaptureScreenLoading({ Key? key, required this.screenName, required this.child, }) : super(key: key); + + /// The UI component whose loading time is being measured. final Widget child; + + /// The name of the screen being monitored for loading performance. final String screenName; @override @@ -22,9 +44,16 @@ class InstabugCaptureScreenLoading extends StatefulWidget { class _InstabugCaptureScreenLoadingState extends State { + /// Trace object that records screen loading details. ScreenLoadingTrace? trace; + + /// Timestamp in microseconds when the widget is created. final startTimeInMicroseconds = IBGDateTime.I.now().microsecondsSinceEpoch; + + /// Monotonic timestamp in microseconds for more precise duration tracking. final startMonotonicTimeInMicroseconds = InstabugMonotonicClock.I.now; + + /// Stopwatch to measure screen loading time. final stopwatch = Stopwatch()..start(); @override @@ -38,7 +67,7 @@ class _InstabugCaptureScreenLoadingState ScreenLoadingManager.I.startScreenLoadingTrace(trace!); - // to maintain supported versions prior to Flutter 3.0.0 + // Ensures compatibility with Flutter versions before 3.0.0 // ignore: invalid_null_aware_operator WidgetsBinding.instance?.addPostFrameCallback((_) { stopwatch.stop(); diff --git a/lib/src/utils/screen_loading/screen_loading_manager.dart b/lib/src/utils/screen_loading/screen_loading_manager.dart index 816ffebef..b01b77627 100644 --- a/lib/src/utils/screen_loading/screen_loading_manager.dart +++ b/lib/src/utils/screen_loading/screen_loading_manager.dart @@ -9,7 +9,10 @@ import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_trace.d import 'package:instabug_flutter/src/utils/screen_loading/ui_trace.dart'; import 'package:meta/meta.dart'; -/// @nodoc +/// Manages screen loading traces and UI traces for performance monitoring. +/// +/// This class handles the tracking of screen loading times and UI transitions, +/// providing an interface for Instabug APM to capture and report performance metrics. @internal class ScreenLoadingManager { ScreenLoadingManager._(); @@ -21,25 +24,33 @@ class ScreenLoadingManager { static ScreenLoadingManager _instance = ScreenLoadingManager._(); + /// Returns the singleton instance of [ScreenLoadingManager]. static ScreenLoadingManager get instance => _instance; /// Shorthand for [instance] static ScreenLoadingManager get I => instance; + + /// Logging tag for debugging purposes. static const tag = "ScreenLoadingManager"; + + /// Stores the current UI trace. UiTrace? currentUiTrace; + + /// Stores the current screen loading trace. ScreenLoadingTrace? currentScreenLoadingTrace; - /// @nodoc + /// Stores prematurely ended traces for debugging purposes. @internal final List prematurelyEndedTraces = []; + /// Allows setting a custom instance for testing. @visibleForTesting // ignore: use_setters_to_change_properties static void setInstance(ScreenLoadingManager instance) { _instance = instance; } - /// @nodoc + /// Resets the flag indicating a screen loading trace has started. @internal void resetDidStartScreenLoading() { // Allows starting a new screen loading capture trace in the same ui trace (without navigating out and in to the same screen) @@ -59,9 +70,8 @@ class ScreenLoadingManager { ); } - /// @nodoc + /// Checks if the Instabug SDK is built before calling API methods. Future _checkInstabugSDKBuilt(String apiName) async { - // Check if Instabug SDK is Built final isInstabugSDKBuilt = await Instabug.isBuilt(); if (!isInstabugSDKBuilt) { InstabugLogger.I.e( @@ -73,7 +83,7 @@ class ScreenLoadingManager { return isInstabugSDKBuilt; } - /// @nodoc + /// Resets the flag indicating a screen loading trace has been reported. @internal void resetDidReportScreenLoading() { // Allows reporting a new screen loading capture trace in the same ui trace even if one was reported before by resetting the flag which is used for checking. @@ -84,7 +94,7 @@ class ScreenLoadingManager { ); } - /// @nodoc + /// Starts a new UI trace with a given screen name. @internal void resetDidExtendScreenLoading() { // Allows reporting a new screen loading capture trace in the same ui trace even if one was reported before by resetting the flag which is used for checking. @@ -176,7 +186,7 @@ class ScreenLoadingManager { } } - /// @nodoc + /// Starts a screen loading trace. @internal Future startScreenLoadingTrace(ScreenLoadingTrace trace) async { try { @@ -225,7 +235,7 @@ class ScreenLoadingManager { } } - /// @nodoc + /// Reports the input [ScreenLoadingTrace] to the native side. @internal Future reportScreenLoading(ScreenLoadingTrace? trace) async { try { diff --git a/pigeons/instabug.api.dart b/pigeons/instabug.api.dart index c0187acb9..275306987 100644 --- a/pigeons/instabug.api.dart +++ b/pigeons/instabug.api.dart @@ -74,4 +74,6 @@ abstract class InstabugHostApi { Map isW3CFeatureFlagsEnabled(); void willRedirectToStore(); + + void setNetworkLogBodyEnabled(bool isEnabled); } diff --git a/pubspec.yaml b/pubspec.yaml index 7b2a91e17..ca47ba5ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: instabug_flutter -version: 14.1.0 +version: 14.3.0 description: >- Instabug empowers mobile teams to monitor, prioritize, and debug performance and stability issues throughout the app development lifecycle. diff --git a/test/network_logger_test.dart b/test/network_logger_test.dart index 77f5de51d..cbdc2db93 100644 --- a/test/network_logger_test.dart +++ b/test/network_logger_test.dart @@ -229,4 +229,14 @@ void main() { await logger.networkLog(networkData); expect(networkData.requestHeaders['traceparent'], 'test'); }); + + test('[setNetworkLogBodyEnabled] should call host method', () async { + const enabled = true; + + await NetworkLogger.setNetworkLogBodyEnabled(enabled); + + verify( + mInstabugHost.setNetworkLogBodyEnabled(enabled), + ).called(1); + }); } diff --git a/test/utils/screen_loading/instabug_capture_screen_loading_test.dart b/test/utils/screen_loading/instabug_capture_screen_loading_test.dart new file mode 100644 index 000000000..26fc609d7 --- /dev/null +++ b/test/utils/screen_loading/instabug_capture_screen_loading_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_manager.dart'; +import 'package:mockito/mockito.dart'; +import '../instabug_navigator_observer_test.mocks.dart'; + +void main() { + late MockScreenLoadingManager mockScreenLoadingManager; + + setUp(() { + mockScreenLoadingManager = MockScreenLoadingManager(); + ScreenLoadingManager.setInstance(mockScreenLoadingManager); + }); + + testWidgets( + 'InstabugCaptureScreenLoading starts and reports screen loading trace', + (WidgetTester tester) async { + const screenName = "/TestScreen"; + + when(mockScreenLoadingManager.sanitizeScreenName(screenName)) + .thenReturn(screenName); + when(mockScreenLoadingManager.startScreenLoadingTrace(any)) + .thenAnswer((_) async {}); + when(mockScreenLoadingManager.reportScreenLoading(any)) + .thenAnswer((_) async {}); + + await tester.pumpWidget( + MaterialApp( + home: InstabugCaptureScreenLoading( + screenName: screenName, + child: Container(), + ), + ), + ); + + verify(mockScreenLoadingManager.startScreenLoadingTrace(any)).called(1); + await tester.pumpAndSettle(); + verify(mockScreenLoadingManager.reportScreenLoading(any)).called(1); + }); +} diff --git a/test/utils/screen_loading/screen_loading_trace_test.dart b/test/utils/screen_loading/screen_loading_trace_test.dart new file mode 100644 index 000000000..e676959ba --- /dev/null +++ b/test/utils/screen_loading/screen_loading_trace_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_flutter/src/utils/screen_loading/screen_loading_trace.dart'; + +void main() { + test( + 'ScreenLoadingTrace copyWith method should keep original values when no override happens', + () { + final trace = ScreenLoadingTrace( + 'TestScreen', + startTimeInMicroseconds: 1000, + startMonotonicTimeInMicroseconds: 2000, + endTimeInMicroseconds: 3000, + duration: 4000, + ); + + final updatedTrace = trace.copyWith(); + + expect(updatedTrace.screenName, 'TestScreen'); + expect(updatedTrace.startTimeInMicroseconds, 1000); + expect(updatedTrace.startMonotonicTimeInMicroseconds, 2000); + expect(updatedTrace.endTimeInMicroseconds, 3000); + expect(updatedTrace.duration, 4000); + }); + + test('ScreenLoadingTrace copyWith method updates fields correctly', () { + final trace = ScreenLoadingTrace( + 'TestScreen', + startTimeInMicroseconds: 1000, + startMonotonicTimeInMicroseconds: 2000, + endTimeInMicroseconds: 3000, + duration: 4000, + ); + + final updatedTrace = trace.copyWith( + startTimeInMicroseconds: 1500, + startMonotonicTimeInMicroseconds: 2500, + endTimeInMicroseconds: 3500, + duration: 4500, + ); + + expect(updatedTrace.screenName, 'TestScreen'); + expect(updatedTrace.startTimeInMicroseconds, 1500); + expect(updatedTrace.startMonotonicTimeInMicroseconds, 2500); + expect(updatedTrace.endTimeInMicroseconds, 3500); + expect(updatedTrace.duration, 4500); + }); + + test('ScreenLoadingTrace toString method returns correct format', () { + final trace = ScreenLoadingTrace( + 'TestScreen', + startTimeInMicroseconds: 1000, + startMonotonicTimeInMicroseconds: 2000, + endTimeInMicroseconds: 3000, + duration: 4000, + ); + + expect( + trace.toString(), + 'ScreenLoadingTrace{screenName: TestScreen, startTimeInMicroseconds: 1000, startMonotonicTimeInMicroseconds: 2000, endTimeInMicroseconds: 3000, duration: 4000}', + ); + }); +} diff --git a/test/utils/screen_loading/ui_trace_test.dart b/test/utils/screen_loading/ui_trace_test.dart new file mode 100644 index 000000000..11ed57c66 --- /dev/null +++ b/test/utils/screen_loading/ui_trace_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter/src/utils/screen_loading/ui_trace.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'screen_loading_manager_test.mocks.dart'; + +@GenerateMocks([RouteMatcher]) +void main() { + test('UiTrace copyWith method updates fields correctly', () { + final trace = UiTrace( + screenName: 'TestScreen', + traceId: 123, + matchingScreenName: 'MatchingScreen', + ); + + final updatedTrace = trace.copyWith( + screenName: 'UpdatedScreen', + traceId: 456, + ); + + expect(updatedTrace.screenName, 'UpdatedScreen'); + expect(updatedTrace.traceId, 456); + }); + + test('UiTrace matches method returns correct result', () { + final mockRouteMatcher = MockRouteMatcher(); + RouteMatcher.setInstance(mockRouteMatcher); + when( + mockRouteMatcher.match( + routePath: 'test/path', + actualPath: 'MatchingScreen', + ), + ).thenReturn(true); + + final trace = UiTrace( + screenName: 'TestScreen', + traceId: 123, + matchingScreenName: 'MatchingScreen', + ); + + expect(trace.matches('test/path'), isTrue); + }); + + test('UiTrace toString method returns correct format', () { + final trace = UiTrace( + screenName: 'TestScreen', + traceId: 123, + matchingScreenName: 'MatchingScreen', + ); + + expect( + trace.toString(), + 'UiTrace{screenName: TestScreen, traceId: 123, isFirstScreenLoadingReported: false, isFirstScreenLoading: false}', + ); + }); +}