"""AAO Career Center adapter — click-based AJAX pagination.""" from __future__ import annotations from loguru import logger from gimme_job.adapters.base import ManifestDrivenAdapter from gimme_job.adapters.registry import register from gimme_job.models.dto import JobPostingCandidate, RawJobCard @register("aaoinfo") class AAOInfoAdapter(ManifestDrivenAdapter): _BASE_URL = "https://careers.aaoinfo.org" def collect_cards(self, page, manifest): try: page.wait_for_selector("div.job-tile", timeout=15000, state="attached") except Exception: logger.debug("[aaoinfo] Results wait timed out — may be zero results") return super().collect_cards(page, manifest) def paginate(self, page, page_index: int, config) -> bool: """Click the next page number button in the AJAX pagination.""" if page_index >= config.pagination.max_pages - 1: return False next_page = page_index + 2 # Capture current first job URL to detect when content actually changes try: first_tile = page.query_selector("div.job-tile .job-title a") current_first_url = first_tile.get_attribute("href") if first_tile else None except Exception: current_first_url = None # Determine which selector to use for the next page button primary_sel = f"#page-item-{next_page} a" fallback_sel = "ul.pagination li.page-item:not(.active):not(.disabled):last-child a" if page.query_selector(primary_sel): btn_sel = primary_sel elif page.query_selector(fallback_sel): btn_sel = fallback_sel else: logger.debug(f"[aaoinfo] No next page button found — stopping at page {page_index + 1}") return False logger.debug(f"[aaoinfo] Paginating to page {next_page}") # Use page.click() (locator-based) so Playwright re-queries the element # at click time, avoiding "not attached to DOM" errors after AJAX refresh. page.click(btn_sel) # Wait for page indicator to become active try: page.wait_for_selector(f"#page-item-{next_page}.active", timeout=12000) except Exception: logger.debug(f"[aaoinfo] Page {next_page} did not become active — end of pagination") return False # Wait until the content actually changes (first job URL differs from before) if current_first_url: try: page.wait_for_function( f"""() => {{ const a = document.querySelector('div.job-tile .job-title a'); return a && a.getAttribute('href') !== {repr(current_first_url)}; }}""", timeout=5000, ) except Exception: logger.debug(f"[aaoinfo] Content did not change after pagination to page {next_page}") return True def normalize(self, raw: RawJobCard) -> JobPostingCandidate: candidate = super().normalize(raw) if candidate.job_url and candidate.job_url.startswith("/"): candidate.job_url = self._BASE_URL + candidate.job_url return candidate