diff --git a/src/auto_answer/runner.py b/src/auto_answer/runner.py index 0ed160b..34af013 100644 --- a/src/auto_answer/runner.py +++ b/src/auto_answer/runner.py @@ -229,12 +229,15 @@ class AnswerRunner: while count < 0 or answered < count: try: self._wait_before_answer() - answer = self._reveal_and_read_answer(page) + answer, revealed_analysis = self._reveal_and_read_answer(page) total = "until end" if count < 0 else str(count) print(f"Answering question {answered + 1}/{total}: {answer}") - self._select_answer_options(page, answer) - self._wait_after_answer() - self._verify_answer_result(page, answer) + if revealed_analysis: + print("Answer analysis opened before selection; skipping selection verification.") + else: + self._select_answer_options(page, answer) + self._wait_after_answer() + self._verify_answer_result(page, answer) if not self._go_next_question(page): print("No next question detected. Stopping.") return @@ -272,11 +275,22 @@ class AnswerRunner: minimum, maximum = maximum, minimum return minimum, maximum - def _reveal_and_read_answer(self, page) -> str: + def _reveal_and_read_answer(self, page) -> tuple[str, bool]: body = page.locator("body") text = body.inner_text(timeout=self.settings.timeout_ms) + revealed_analysis = False + raw_answer = self._read_correct_answer_from_dom(page) + if raw_answer: + return self._parse_answer_text(raw_answer), revealed_analysis + + if not re.search(r"正确答案\s*[::]", text): + text = page.evaluate( + "() => document.body ? document.body.textContent || '' : ''" + ) + if not re.search(r"正确答案\s*[::]", text): page.get_by_text("答案解析", exact=True).click() + revealed_analysis = True page.wait_for_function( "() => /正确答案\\s*[::]/.test(document.body.innerText)", timeout=self.settings.timeout_ms, @@ -295,7 +309,33 @@ class AnswerRunner: ) raw_answer = match.group(1).strip() - return self._parse_answer_text(raw_answer) + return self._parse_answer_text(raw_answer), revealed_analysis + + def _read_correct_answer_from_dom(self, page) -> str: + return self._read_answer_field_from_dom(page, "正确答案") + + def _read_my_answer_from_dom(self, page) -> str: + return self._read_answer_field_from_dom(page, "我的答案") + + def _read_answer_field_from_dom(self, page, field_name: str) -> str: + return page.evaluate( + r"""fieldName => { + const pattern = new RegExp(`^${fieldName}\\s*[::]$`); + for (const span of document.querySelectorAll("span")) { + const label = (span.textContent || "").trim(); + if (!pattern.test(label)) continue; + + const parent = span.parentElement; + if (!parent) continue; + + const valueNode = parent.querySelector("i"); + const value = (valueNode?.textContent || "").trim(); + if (value) return value; + } + return ""; + }""", + field_name, + ).strip() def _parse_answer_text(self, raw_answer: str) -> str: answer_text = self._normalize_answer_text(raw_answer) @@ -426,6 +466,19 @@ class AnswerRunner: raise PageDebugPrintedError(f"Text answer option not found: {answer}") def _verify_answer_result(self, page, expected_answer: str) -> None: + raw_actual_answer = self._read_my_answer_from_dom(page) + if raw_actual_answer: + actual_answer = self._parse_answer_text(raw_actual_answer) + if actual_answer != expected_answer: + self._print_debug_page( + page, + f"Selected answer mismatch: expected={expected_answer}, actual={actual_answer}", + ) + raise PageDebugPrintedError( + f"Selected answer mismatch: expected={expected_answer}, actual={actual_answer}" + ) + return + text = page.locator("body").inner_text(timeout=self.settings.timeout_ms) match = re.search( r"我的答案\s*[::]\s*(.*?)(?=\s*(?:正确答案|参考解析|答案解析|答题卡|$))",