|
@@ -212,7 +212,7 @@ class TlsPlugin(IVSPlg):
|
|
|
filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
|
|
filename = f"{self.instance_id}_{name_prefix}_{timestamp}.jpg"
|
|
|
save_path = os.path.join("data", filename)
|
|
save_path = os.path.join("data", filename)
|
|
|
os.makedirs("data", exist_ok=True)
|
|
os.makedirs("data", exist_ok=True)
|
|
|
- self.page.screenshot(path=save_path, full_page=False)
|
|
|
|
|
|
|
+ self.page.screenshot(path=save_path, full_page=True)
|
|
|
self._log(f"Screenshot saved to {save_path}")
|
|
self._log(f"Screenshot saved to {save_path}")
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
self._log(f"Failed to save screenshot: {e}")
|
|
self._log(f"Failed to save screenshot: {e}")
|
|
@@ -585,8 +585,6 @@ class TlsPlugin(IVSPlg):
|
|
|
target_month_num = target_date_obj.month
|
|
target_month_num = target_date_obj.month
|
|
|
|
|
|
|
|
slots = []
|
|
slots = []
|
|
|
- all_slots = []
|
|
|
|
|
-
|
|
|
|
|
current_selected_ele = self.page.locator('[data-testid="btn-current-month-available"]').first
|
|
current_selected_ele = self.page.locator('[data-testid="btn-current-month-available"]').first
|
|
|
current_month_text = current_selected_ele.inner_text().strip() if current_selected_ele.count() else ""
|
|
current_month_text = current_selected_ele.inner_text().strip() if current_selected_ele.count() else ""
|
|
|
|
|
|
|
@@ -594,58 +592,35 @@ class TlsPlugin(IVSPlg):
|
|
|
|
|
|
|
|
if not is_on_target_month:
|
|
if not is_on_target_month:
|
|
|
self._log(f"Current is '{current_month_text}', navigating to '{target_month_text}'...")
|
|
self._log(f"Current is '{current_month_text}', navigating to '{target_month_text}'...")
|
|
|
- for _ in range(12):
|
|
|
|
|
- target_btn = self.page.locator(f"a[href*='month={interest_month}']").first
|
|
|
|
|
-
|
|
|
|
|
- if target_btn.count():
|
|
|
|
|
- target_btn.click(timeout=5000)
|
|
|
|
|
- time.sleep(3)
|
|
|
|
|
|
|
+ reached_target = False
|
|
|
|
|
+ for step in range(12):
|
|
|
|
|
+ current_ele = self.page.locator('[data-testid="btn-current-month-available"]').first
|
|
|
|
|
+ if current_ele.count() and current_ele.inner_text().strip().lower() == target_month_text.lower():
|
|
|
|
|
+ self._log(f"✅ Successfully navigated to target month: '{target_month_text}'!")
|
|
|
|
|
+ reached_target = True
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
next_btn = self.page.locator('[data-testid="btn-next-month-available"]').first
|
|
next_btn = self.page.locator('[data-testid="btn-next-month-available"]').first
|
|
|
|
|
+
|
|
|
if next_btn.count():
|
|
if next_btn.count():
|
|
|
next_btn.click(timeout=5000)
|
|
next_btn.click(timeout=5000)
|
|
|
- time.sleep(2)
|
|
|
|
|
|
|
+ time.sleep(2.5)
|
|
|
else:
|
|
else:
|
|
|
- self._log("Warning: Cannot find target month or 'Next Month' button.")
|
|
|
|
|
|
|
+ self._log("⚠️ Reached the end of the calendar or 'Next Month' is disabled.")
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
- try:
|
|
|
|
|
- self.page.wait_for_load_state("networkidle", timeout=20000)
|
|
|
|
|
- except PlaywrightTimeoutError:
|
|
|
|
|
- try:
|
|
|
|
|
- self.page.wait_for_load_state("domcontentloaded", timeout=10000)
|
|
|
|
|
- self.save_screenshot("query_load_timeout")
|
|
|
|
|
- except PlaywrightTimeoutError:
|
|
|
|
|
- pass
|
|
|
|
|
- time.sleep(0.8)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ if not reached_target:
|
|
|
|
|
+ self._log(f"❌ Could not navigate to target month: {target_month_text}. Stop parsing.")
|
|
|
|
|
+ res.success = False
|
|
|
|
|
+ res.availability_status = AvailabilityStatus.NoneAvailable
|
|
|
|
|
+ return res
|
|
|
self._log("Extracting slots from DOM using robust data-testid features...")
|
|
self._log("Extracting slots from DOM using robust data-testid features...")
|
|
|
- all_slots = self._extract_slots_from_calendar_dom(
|
|
|
|
|
- target_year, target_month_num
|
|
|
|
|
- )
|
|
|
|
|
- if not all_slots:
|
|
|
|
|
- n_slot_btns = self.page.locator("[data-testid*='slot']").count()
|
|
|
|
|
- n_avail = self.page.locator("button[data-testid^='btn-available-slot']").count()
|
|
|
|
|
- self._log(
|
|
|
|
|
- f"DOM 日历未解析到槽位: [data-testid*=\"slot\"]={n_slot_btns}, "
|
|
|
|
|
- f"btn-available-slot={n_avail},回退为页面 HTML 内嵌 JSON 解析"
|
|
|
|
|
- )
|
|
|
|
|
- try:
|
|
|
|
|
- resp = self._perform_request("GET", self.page.url, retry_count=1)
|
|
|
|
|
- self._check_page_is_session_expired_or_invalid("Book your appointment", resp.text)
|
|
|
|
|
- all_slots = self._parse_appointment_slots(resp.text)
|
|
|
|
|
- except Exception as ex:
|
|
|
|
|
- self._log(f"HTML 回退解析失败: {ex}")
|
|
|
|
|
-
|
|
|
|
|
|
|
+ slots = self._extract_slots_from_calendar_dom(target_year, target_month_num)
|
|
|
else:
|
|
else:
|
|
|
self._log(f"Already on '{target_month_text}'. Executing silent JS fetch...")
|
|
self._log(f"Already on '{target_month_text}'. Executing silent JS fetch...")
|
|
|
resp = self._perform_request("GET", self.page.url, retry_count=1)
|
|
resp = self._perform_request("GET", self.page.url, retry_count=1)
|
|
|
self._check_page_is_session_expired_or_invalid('Book your appointment', resp.text)
|
|
self._check_page_is_session_expired_or_invalid('Book your appointment', resp.text)
|
|
|
- all_slots = self._parse_appointment_slots(resp.text)
|
|
|
|
|
-
|
|
|
|
|
- target_labels = self.free_config.get("target_labels", ["", "pta"])
|
|
|
|
|
- slots = [s for s in all_slots if s.get("label") in target_labels]
|
|
|
|
|
|
|
+ slots = self._parse_appointment_slots(resp.text)
|
|
|
|
|
|
|
|
if slots:
|
|
if slots:
|
|
|
res.success = True
|
|
res.success = True
|