@@ -259,7 +259,6 @@ def get_screen_text_content(obj):
259259 pass # Error getting text
260260 return texts
261261
262-
263262def verify_text_present (obj , expected_text ):
264263 """
265264 Verify that expected text is present somewhere on screen.
@@ -281,6 +280,87 @@ def verify_text_present(obj, expected_text):
281280 return find_label_with_text (obj , expected_text ) is not None
282281
283282
283+ def find_setting_value_label (obj , setting_title_text ):
284+ """
285+ Find the value label associated with a SettingsActivity setting title.
286+
287+ SettingsActivity renders each setting as a container with two labels:
288+ a title label (large) and a value label (smaller) directly below it.
289+ This helper finds the title label, then returns the sibling value label.
290+
291+ Args:
292+ obj: LVGL object to search (typically lv.screen_active())
293+ setting_title_text: Text of the setting title (exact or substring)
294+
295+ Returns:
296+ LVGL label object for the value if found, None otherwise
297+
298+ Example:
299+ value_label = find_setting_value_label(lv.screen_active(), "Auth Mode")
300+ if value_label:
301+ assert value_label.get_text() == "(defaults to none)"
302+ """
303+ title_label = find_label_with_text (obj , setting_title_text )
304+ if not title_label :
305+ return None
306+ try :
307+ parent = title_label .get_parent ()
308+ if not parent :
309+ return None
310+ child_count = parent .get_child_count ()
311+ for i in range (child_count ):
312+ child = parent .get_child (i )
313+ if child is title_label :
314+ continue
315+ try :
316+ if hasattr (child , "get_text" ):
317+ text = child .get_text ()
318+ if text :
319+ return child
320+ except :
321+ pass
322+ except :
323+ pass
324+ return None
325+
326+
327+ def get_setting_value_text (obj , setting_title_text ):
328+ """
329+ Get the value text associated with a SettingsActivity setting title.
330+
331+ Args:
332+ obj: LVGL object to search (typically lv.screen_active())
333+ setting_title_text: Text of the setting title (exact or substring)
334+
335+ Returns:
336+ str or None: The value label text if found
337+ """
338+ value_label = find_setting_value_label (obj , setting_title_text )
339+ if value_label :
340+ try :
341+ return value_label .get_text ()
342+ except :
343+ return None
344+ return None
345+
346+
347+ def verify_setting_value_text (obj , setting_title_text , expected_text ):
348+ """
349+ Verify a SettingsActivity value label matches expected text.
350+
351+ Args:
352+ obj: LVGL object to search (typically lv.screen_active())
353+ setting_title_text: Text of the setting title (exact or substring)
354+ expected_text: Expected text for the value label (exact match)
355+
356+ Returns:
357+ bool: True if value label text matches expected, False otherwise
358+ """
359+ value_text = get_setting_value_text (obj , setting_title_text )
360+ return value_text == expected_text
361+
362+
363+
284364def text_to_hex (text ):
285365 """
286366 Convert text to hex representation for debugging.
@@ -414,6 +494,116 @@ def find_button_with_text(obj, search_text):
414494 return None
415495
416496
497+ def find_dropdown_widget (obj ):
498+ """
499+ Find a dropdown widget in the object hierarchy.
500+
501+ Args:
502+ obj: LVGL object to search (typically lv.screen_active())
503+
504+ Returns:
505+ LVGL dropdown object if found, None otherwise
506+ """
507+ def find_dropdown_recursive (node ):
508+ try :
509+ if node .__class__ .__name__ == "dropdown" or hasattr (node , "get_selected" ):
510+ if hasattr (node , "get_options" ):
511+ return node
512+ except :
513+ pass
514+
515+ try :
516+ child_count = node .get_child_count ()
517+ except :
518+ return None
519+
520+ for i in range (child_count ):
521+ child = node .get_child (i )
522+ result = find_dropdown_recursive (child )
523+ if result :
524+ return result
525+ return None
526+
527+ return find_dropdown_recursive (obj )
528+
529+
530+ def get_dropdown_options (dropdown ):
531+ """
532+ Get dropdown options as a list of strings.
533+
534+ Args:
535+ dropdown: LVGL dropdown widget
536+
537+ Returns:
538+ list: List of option strings (order preserved)
539+ """
540+ try :
541+ options = dropdown .get_options ()
542+ if options :
543+ lines = options .split ("\n " )
544+ return [line for line in lines if line ]
545+ except :
546+ pass
547+ return []
548+
549+
550+ def find_dropdown_option_index (dropdown , option_text , allow_partial = True ):
551+ """
552+ Find the index of an option in a dropdown by text.
553+
554+ Args:
555+ dropdown: LVGL dropdown widget
556+ option_text: Text to search for
557+ allow_partial: If True, match substring (default: True)
558+
559+ Returns:
560+ int or None: Index of matching option
561+ """
562+ options = get_dropdown_options (dropdown )
563+ if options :
564+ for idx , text in enumerate (options ):
565+ if (allow_partial and option_text in text ) or (not allow_partial and option_text == text ):
566+ return idx
567+ return None
568+
569+ try :
570+ option_count = dropdown .get_option_count ()
571+ except :
572+ option_count = 0
573+
574+ for idx in range (option_count ):
575+ try :
576+ text = dropdown .get_option_text (idx )
577+ if (allow_partial and option_text in text ) or (not allow_partial and option_text == text ):
578+ return idx
579+ except :
580+ pass
581+
582+ return None
583+
584+
585+ def select_dropdown_option_by_text (dropdown , option_text , allow_partial = True ):
586+ """
587+ Select a dropdown option by its text.
588+
589+ Args:
590+ dropdown: LVGL dropdown widget
591+ option_text: Text to select
592+ allow_partial: If True, match substring (default: True)
593+
594+ Returns:
595+ bool: True if option was found and selected
596+ """
597+ idx = find_dropdown_option_index (dropdown , option_text , allow_partial = allow_partial )
598+ if idx is None :
599+ return False
600+ try :
601+ dropdown .set_selected (idx )
602+ return True
603+ except :
604+ return False
605+
606+
417607def get_keyboard_button_coords (keyboard , button_text ):
418608 """
419609 Get the coordinates of a specific button on an LVGL keyboard/buttonmatrix.
0 commit comments