From aa5d3bf2d5f8d49160734a08ba23cf3ee41292a8 Mon Sep 17 00:00:00 2001 From: Gitead Date: Fri, 16 Oct 2026 23:13:36 -0400 Subject: [PATCH] fix: Deltains claim tooth surface multi-select Open the toothSurfaces MUI listbox (retrying if it doesn't appear), check each surface letter by option text via JS and verify it ends up selected, then close the popover with Escape or a backdrop click so its overlay no longer blocks the fee field. Waits for the tooth-number listbox to close first so its options aren't misread. Co-Authored-By: Claude Opus 5.5 --- .../selenium_DeltaIns_claimSubmitWorker.py | 105 +++++++++++++++--- 1 file changed, 87 insertions(+), 18 deletions(-) diff --git a/apps/SeleniumService/selenium_DeltaIns_claimSubmitWorker.py b/apps/SeleniumService/selenium_DeltaIns_claimSubmitWorker.py index 7a6f2d94..bac8fc1b 100644 --- a/apps/SeleniumService/selenium_DeltaIns_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_DeltaIns_claimSubmitWorker.py @@ -505,6 +505,83 @@ class AutomationDeltaInsClaimSubmit: print(f"[Deltains Claim step4] Warning: could not fill {label} (#{element_id}): {e}") return None + _SURFACE_OPTIONS_JS = """ + const box = document.querySelector('[role="listbox"]'); + if (!box) return null; + return Array.from(box.querySelectorAll('[role="option"], li')).map(o => { + const cb = o.querySelector('input[type="checkbox"]'); + return { + text: (o.textContent || '').trim().toUpperCase(), + selected: o.getAttribute('aria-selected') === 'true' || !!(cb && cb.checked), + }; + }); + """ + + _SURFACE_CLICK_JS = """ + const letter = arguments[0]; + const box = document.querySelector('[role="listbox"]'); + if (!box) return false; + const opt = Array.from(box.querySelectorAll('[role="option"], li')) + .find(o => (o.textContent || '').trim().toUpperCase() === letter); + if (!opt) return false; + opt.scrollIntoView({block: 'nearest'}); + opt.click(); + return true; + """ + + def _select_tooth_surfaces(self, surface_select, letters, idx): + """Open the toothSurfaces multi-select and check each letter, verifying + the option ends up selected (aria-selected / checkbox checked).""" + # Make sure the tooth-number listbox is gone so we don't read its options + self._close_mui_popover() + options = None + for _ in range(3): + surface_select.click() + try: + options = WebDriverWait(self.driver, 4).until( + lambda d: d.execute_script(self._SURFACE_OPTIONS_JS) or False) + break + except TimeoutException: + print(f"[Deltains Claim step4] surface[{idx}]: listbox did not open, retrying") + if not options: + print(f"[Deltains Claim step4] surface[{idx}]: could not open surface listbox") + return + print(f"[Deltains Claim step4] surface[{idx}]: options={[o['text'] for o in options]}") + + for letter in letters: + state = {o["text"]: o["selected"] for o in (self.driver.execute_script(self._SURFACE_OPTIONS_JS) or [])} + if letter not in state: + print(f"[Deltains Claim step4] surface[{idx}]: option '{letter}' not found") + continue + if state[letter]: + print(f"[Deltains Claim step4] surface[{idx}]: '{letter}' already checked") + continue + self.driver.execute_script(self._SURFACE_CLICK_JS, letter) + time.sleep(0.3) + state = {o["text"]: o["selected"] for o in (self.driver.execute_script(self._SURFACE_OPTIONS_JS) or [])} + print(f"[Deltains Claim step4] surface[{idx}]: '{letter}' checked={state.get(letter)}") + + shown = (surface_select.text or "").strip() + print(f"[Deltains Claim step4] surface[{idx}]: field now shows {shown!r}") + + def _close_mui_popover(self): + """Close any open MUI Select popover so its backdrop stops covering + the rest of the form.""" + for attempt in range(3): + if not self.driver.find_elements(By.CSS_SELECTOR, '[role="listbox"]'): + return + try: + if attempt == 0: + self.driver.switch_to.active_element.send_keys(Keys.ESCAPE) + else: + self.driver.execute_script(""" + const bd = document.querySelector('.MuiPopover-root .MuiBackdrop-root, .MuiMenu-root .MuiBackdrop-root'); + if (bd) bd.click(); + """) + except Exception as e: + print(f"[Deltains Claim step4] close popover attempt {attempt} failed: {e}") + time.sleep(0.4) + def step4_fill_claim_form(self): """ Fill each service line: date of service, procedure code, and fee @@ -582,34 +659,26 @@ class AutomationDeltaInsClaimSubmit: except Exception as e: print(f"[Deltains Claim step4] Could not fill tooth number: {e}") - # Tooth surfaces — MUI multi-select; each letter (B, D, L, M, O...) - # is a checkbox option that stays open until clicked elsewhere. + # Tooth surfaces — MUI multi-select; each letter (B, D, F, I, L, + # M, O) is a checkbox option in a popover listbox that stays + # open until Escape / a backdrop click. if surface: try: surface_select = WebDriverWait(self.driver, 8).until( EC.element_to_be_clickable((By.ID, f"serviceLines.{idx}.toothSurfaces")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", surface_select) - surface_select.click() - time.sleep(0.5) - surface_letters = [c for c in surface if c.isalpha()] - for letter in surface_letters: - try: - opt = WebDriverWait(self.driver, 5).until( - EC.element_to_be_clickable((By.XPATH, - f"//li[@role='option'][normalize-space(.)='{letter}']")) - ) - opt.click() - time.sleep(0.2) - print(f"[Deltains Claim step4] surface[{idx}]: checked '{letter}'") - except TimeoutException: - print(f"[Deltains Claim step4] surface[{idx}]: option '{letter}' not found") - self.driver.find_element(By.TAG_NAME, "body").send_keys(Keys.ESCAPE) - time.sleep(0.3) + surface_letters = [] + for c in surface: + if c.isalpha() and c not in surface_letters: + surface_letters.append(c) + self._select_tooth_surfaces(surface_select, surface_letters, idx) except TimeoutException: print(f"[Deltains Claim step4] surface[{idx}]: select not present for this procedure code") except Exception as e: print(f"[Deltains Claim step4] Could not fill tooth surfaces: {e}") + finally: + self._close_mui_popover() if billed: self._fill_by_id(f"serviceLines.{idx}.fee", billed, f"fee[{idx}]")