diff --git a/fccs_export.py b/fccs_export.py index 15cf6a3..0720d7d 100755 --- a/fccs_export.py +++ b/fccs_export.py @@ -93,15 +93,53 @@ def connect_main(ctrl, log): # PER-DRAWER EXPORT # --------------------------------------------------------------------------- -def navigate_to_drawer(main, drawer_id, ctrl, timeouts, log): - """Type the drawer ID into the search box and click Go.""" - # Use best_match name "Edit" instead of title — the title is placeholder - # text that changes once the user types in the box. - box = main["Edit"] - box.wait("visible ready", timeout=timeouts["nav"]) +def find_search_box(main, ctrl, timeouts, log): + """Locate the drawer search box reliably using the Go button as an anchor. - box.set_edit_text("") - box.set_edit_text(drawer_id) + The main window has several Edit controls (notably the data-location box + inside a ComboBox). Their best_match names ("Edit", "Edit0"...) are + unstable and the placeholder title only exists in a fresh session, so we + anchor off the Go button instead: the search box sits on the same row + (same top Y) as Go, immediately to its left. Call once before the drawer + loop — the returned wrapper stays valid for the window's lifetime. + """ + go = main.child_window(title=ctrl["go_button_title"], class_name="Button") + go.wait("visible ready", timeout=timeouts["nav"]) + go_rect = go.rectangle() + + edits = main.children(class_name="Edit") + if not edits: + edits = main.descendants(class_name="Edit") + + tolerance = 10 + candidates = [ + e for e in edits + if abs(e.rectangle().top - go_rect.top) <= tolerance + ] + if not candidates: + raise RuntimeError( + "Could not find the drawer search box (no Edit control on the " + "Go button's row). FCCS may not be on the main drawer view." + ) + + # If several share the row, pick the one whose right edge is closest to + # (and left of) the Go button — that's the box directly beside Go. + left_of_go = [e for e in candidates if e.rectangle().right <= go_rect.left] + pool = left_of_go if left_of_go else candidates + box = max(pool, key=lambda e: e.rectangle().right) + + r = box.rectangle() + log(f" Search box located at {(r.left, r.top, r.right, r.bottom)} " + f"(Go button top={go_rect.top})") + return box + + +def navigate_to_drawer(main, search_box, drawer_id, ctrl, timeouts, log): + """Type the drawer ID into the search box and click Go.""" + search_box.wait("visible ready", timeout=timeouts["nav"]) + + search_box.set_edit_text("") + search_box.set_edit_text(drawer_id) time.sleep(timeouts["settle"]) main.child_window(title=ctrl["go_button_title"], class_name="Button").click() @@ -257,8 +295,8 @@ def wait_for_export(app, ctrl, timeouts, log): return True -def export_drawer(app, main, drawer_id, ctrl, timeouts, screenshot_dir, - manifest_dir, log): +def export_drawer(app, main, search_box, drawer_id, ctrl, timeouts, + screenshot_dir, manifest_dir, log): """Full per-drawer sequence. Returns True on success.""" log(f"Drawer {drawer_id}: starting") @@ -266,7 +304,7 @@ def export_drawer(app, main, drawer_id, ctrl, timeouts, screenshot_dir, dismiss_any_dialog(app, ctrl, log) # 1. Navigate - if not navigate_to_drawer(main, drawer_id, ctrl, timeouts, log): + if not navigate_to_drawer(main, search_box, drawer_id, ctrl, timeouts, log): take_screenshot(main, drawer_id, "nav_fail", screenshot_dir, log) return False @@ -364,6 +402,12 @@ def main(): app, main_win = connect_main(ctrl, log) + try: + search_box = find_search_box(main_win, ctrl, timeouts, log) + except Exception as e: + log(f"ERROR: {e}") + sys.exit(1) + success_count = 0 fail_count = 0 failed_ids = [] @@ -371,8 +415,8 @@ def main(): for i, drawer_id in enumerate(pending, start=1): log(f"--- [{i}/{len(pending)}] Drawer {drawer_id} ---") try: - ok = export_drawer(app, main_win, drawer_id, ctrl, timeouts, - paths["screenshot_dir"], + ok = export_drawer(app, main_win, search_box, drawer_id, ctrl, + timeouts, paths["screenshot_dir"], paths["manifest_dir"], log) except Exception as e: log(f" UNEXPECTED ERROR on drawer {drawer_id}: {e}")