Forum Discussion

oscarmoran's avatar
oscarmoran
Occasional Visitor
23 days ago

RuntimeError: The object does not exist

Hello, I need help with a persistent issue in TestComplete using Python scripting.

I created a helper method with chatGPT that waits for UI objects and executes actions safely. However, I am constantly getting RuntimeError: The object does not exist, even when the UI object is on screen.

The important detail is that this error happens BEFORE my helper executes, meaning TestComplete tries to resolve the alias too early, even if I pass it inside a lambda.

The errors I am getting include:
RuntimeError: The object does not exist

I tried several approaches: using lambda wrappers, string-based alias evaluation with eval, safe exists checks, try/except wrapping, WaitProperty with catch, RefreshMappingInfo, and returning stub objects. Still, TestComplete tries to resolve the alias too early and throws a RuntimeError before my code handles it.

I want to know if TestComplete officially supports passing object references using lambda in Python without resolving them immediately, or if there is a recommended approach for safe deferred resolution of Alias-based UI objects.

Here is the simplified version of my helper (the stable version):

# ============================================================
#  LIB_IfObject.py
#  Helper for safe object waits and actions in TestComplete
# ============================================================

class IfObjectHelper:
    """
    Waits, validates, and executes actions on TestComplete UI objects,
    handling object recreation, timing issues, and temporary unavailability.
    """

    @staticmethod
    def Run(obj, accion=None, timeout=40000, descripcion="object",
            intentos_accion=1, opcional=False):
        """
        Waits until the object exists and optionally executes an action.

        Parameters:
          obj: object reference or lambda returning the object dynamically.
          accion: function/lambda to execute over the object.
          timeout: maximum wait time in milliseconds.
          descripcion: text description for logs.
          intentos_accion: number of retries if the action fails.
          opcional: if True, missing objects do not fail the test (for optional popups).
        """
        try:
            timeout = timeout or 40000
            start = aqDateTime.Now()
            found = False
            resolved_obj = None  

            # === Attempt to resolve the object ===
            for _ in range(3):
                try:
                    resolved_obj = obj() if callable(obj) else obj

                    if not hasattr(resolved_obj, "WaitProperty"):
                        Delay(100)
                        continue

                    resolved_obj.RefreshMappingInfo()

                    # === Handle RuntimeError for dynamic UI objects ===
                    try:
                        # Retry if the object is not instantiated or was recreated
                        if not getattr(resolved_obj, "Exists", False):
                            Delay(200)
                            resolved_obj = obj() if callable(obj) else obj
                            resolved_obj.RefreshMappingInfo()
                    except RuntimeError:
                        # If the handle does not exist yet, wait and retry
                        Delay(300)
                        try:
                            resolved_obj = obj() if callable(obj) else obj
                            resolved_obj.RefreshMappingInfo()
                        except:
                            Delay(100)

                    # === Extended verification of visibility and enabled state ===
                    if (resolved_obj.WaitProperty("Exists", True, timeout) and
                        resolved_obj.WaitProperty("VisibleOnScreen", True, int(timeout / 2))):

                        # If the object exists but is disabled, treat as informational
                        if not resolved_obj.Enabled:
                            Log.Message(f"ℹ {descripcion} found but disabled (action skipped).")
                            return True
                        
                        found = True
                        break

                except Exception:
                    Delay(100)

            # === Handle non-existing object ===
            if not found:
                if opcional or "popup" in descripcion.lower():
                    Log.Message(f"ℹ {descripcion} not found (optional, skipping).")
                    return True
                else:
                    Log.Warning(f"❌ {descripcion} not found after {timeout/1000:.1f}s.")
                    return False

            # === Execute action (if provided) ===
            if accion:
                success = False

                for attempt in range(1, intentos_accion + 1):
                    try:
                        # Validate that the object still exists
                        if not getattr(resolved_obj, "Exists", False):
                            Log.Warning(f"⚠️ {descripcion}: object disappeared before action, retrying...")
                            try:
                                resolved_obj = obj() if callable(obj) else obj
                                resolved_obj.RefreshMappingInfo()
                            except:
                                Delay(200)
                            continue

                        # Handle actions passed as list/tuple
                        if isinstance(accion, (list, tuple)):
                            for sub in accion:
                                try:
                                    sub()
                                except Exception as sub_e:
                                    Log.Warning(f"⚠ Sub-action error for {descripcion}: "
                                                f"{type(sub_e).__name__} - {str(sub_e)}")
                                    Delay(100)
                        else:
                            # Standard single action
                            accion()

                        success = True
                        Log.Checkpoint(f"✅ {descripcion} found and action executed successfully.")
                        break

                    except Exception as e:
                        # Diagnostic block to identify failing object/action
                        try:
                            origin = getattr(resolved_obj, "FullName", str(resolved_obj))
                            Log.Warning(f"⚠ Attempt {attempt}/{intentos_accion} failed in {descripcion}: "
                                        f"{type(e).__name__} - {str(e)} | Object: {origin}")
                        except:
                            Log.Warning(f"⚠ Attempt {attempt}/{intentos_accion} failed in {descripcion}: {str(e)}")

                        Delay(500)
                        try:
                            resolved_obj.RefreshMappingInfo()
                        except:
                            Delay(100)

                if not success:
                    Log.Error(f"❌ Action failed in {descripcion} after {intentos_accion} attempts.")
                    return False

            else:
                Log.Message(f"✔ {descripcion} found (no action executed).")

            # === Total execution time ===
            duration = aqDateTime.TimeInterval(start, aqDateTime.Now())
            Log.Message(f"⏱ Total time for {descripcion}: {duration:.2f} sec.")
            return True

        except Exception as e:
            import traceback
            detail = traceback.format_exc()
            Log.Error(f"General error in {descripcion}: {type(e).__name__} - {str(e)}", detail)
            return False

My questions:
1. Is there an official recommended pattern for safely resolving dynamic alias-based objects in Python for desktop testing?
2. Does TestComplete support passing object references via lambda without resolving them prematurely?
3. Is there any documented workaround for avoiding early alias evaluation inside Python?

Any help will be appreciated. Thank you.

1 Reply