Spaces:
Sleeping
Sleeping
| """Dynamic checks using Playwright.""" | |
| import asyncio | |
| import json | |
| import re | |
| from typing import Any | |
| from playwright.async_api import async_playwright | |
| from shared.logger import setup_logger | |
| logger = setup_logger(__name__) | |
| class DynamicChecker: | |
| """Perform dynamic checks using Playwright.""" | |
| def __init__(self, pages_url: str, checks: list[str]) -> None: | |
| """Initialize dynamic checker. | |
| Args: | |
| pages_url: GitHub Pages URL | |
| checks: List of check descriptions | |
| """ | |
| self.pages_url = pages_url | |
| self.checks = checks | |
| async def run_checks(self) -> list[dict[str, Any]]: | |
| """Run all dynamic checks. | |
| Returns: | |
| List of check results | |
| """ | |
| results = [] | |
| async with async_playwright() as p: | |
| browser = await p.chromium.launch() | |
| page = await browser.new_page() | |
| try: | |
| # Load page | |
| await page.goto(self.pages_url, timeout=30000, wait_until="networkidle") | |
| # Wait a bit for JS to execute | |
| await page.wait_for_timeout(2000) | |
| # Run each check | |
| for check in self.checks: | |
| result = await self._run_single_check(page, check) | |
| results.append(result) | |
| except Exception as e: | |
| logger.error(f"Error loading page {self.pages_url}: {e}") | |
| results.append( | |
| { | |
| "check": "Page Load", | |
| "passed": False, | |
| "score": 0.0, | |
| "reason": f"Failed to load page: {e}", | |
| } | |
| ) | |
| finally: | |
| await browser.close() | |
| return results | |
| async def _run_single_check(self, page, check_desc: str) -> dict[str, Any]: | |
| """Run a single check. | |
| Args: | |
| page: Playwright page | |
| check_desc: Check description | |
| Returns: | |
| Check result | |
| """ | |
| try: | |
| # Parse check type | |
| if check_desc.startswith("js:"): | |
| # JavaScript evaluation check | |
| js_code = check_desc[3:].strip() | |
| return await self._run_js_check(page, js_code) | |
| else: | |
| # Text-based check | |
| return await self._run_text_check(page, check_desc) | |
| except Exception as e: | |
| logger.error(f"Error running check '{check_desc}': {e}") | |
| return { | |
| "check": check_desc, | |
| "passed": False, | |
| "score": 0.0, | |
| "reason": f"Error: {e}", | |
| } | |
| async def _run_js_check(self, page, js_code: str) -> dict[str, Any]: | |
| """Run JavaScript evaluation check. | |
| Args: | |
| page: Playwright page | |
| js_code: JavaScript code to evaluate | |
| Returns: | |
| Check result | |
| """ | |
| try: | |
| result = await page.evaluate(js_code) | |
| if isinstance(result, bool): | |
| return { | |
| "check": f"JS: {js_code[:50]}...", | |
| "passed": result, | |
| "score": 1.0 if result else 0.0, | |
| "reason": f"JavaScript evaluated to {result}", | |
| } | |
| else: | |
| return { | |
| "check": f"JS: {js_code[:50]}...", | |
| "passed": True, | |
| "score": 1.0, | |
| "reason": f"JavaScript returned: {result}", | |
| } | |
| except Exception as e: | |
| return { | |
| "check": f"JS: {js_code[:50]}...", | |
| "passed": False, | |
| "score": 0.0, | |
| "reason": f"JavaScript error: {e}", | |
| } | |
| async def _run_text_check(self, page, check_desc: str) -> dict[str, Any]: | |
| """Run text-based check. | |
| Args: | |
| page: Playwright page | |
| check_desc: Check description | |
| Returns: | |
| Check result | |
| """ | |
| # Extract selector and expected condition | |
| # Examples: | |
| # - "Page title equals 'Sales Summary xyz'" | |
| # - "Element #total-sales exists and displays numeric value" | |
| # - "Bootstrap 5 CSS loaded from jsdelivr" | |
| content = await page.content() | |
| # Check for page title | |
| if "page title" in check_desc.lower(): | |
| title = await page.title() | |
| match = re.search(r"['\"]([^'\"]+)['\"]", check_desc) | |
| if match: | |
| expected = match.group(1) | |
| passed = expected in title | |
| return { | |
| "check": check_desc, | |
| "passed": passed, | |
| "score": 1.0 if passed else 0.0, | |
| "reason": f"Title is '{title}', expected to contain '{expected}'", | |
| } | |
| # Check for element existence | |
| if "element" in check_desc.lower(): | |
| # Extract selector | |
| selector_match = re.search(r"(#[\w-]+|\.[a-z-]+|[a-z]+)", check_desc, re.IGNORECASE) | |
| if selector_match: | |
| selector = selector_match.group(1) | |
| try: | |
| element = await page.query_selector(selector) | |
| if element: | |
| text = await element.text_content() | |
| return { | |
| "check": check_desc, | |
| "passed": True, | |
| "score": 1.0, | |
| "reason": f"Element {selector} found with content: {text[:50]}...", | |
| } | |
| else: | |
| return { | |
| "check": check_desc, | |
| "passed": False, | |
| "score": 0.0, | |
| "reason": f"Element {selector} not found", | |
| } | |
| except Exception as e: | |
| return { | |
| "check": check_desc, | |
| "passed": False, | |
| "score": 0.0, | |
| "reason": f"Error finding element: {e}", | |
| } | |
| # Check for Bootstrap/library loading | |
| if "bootstrap" in check_desc.lower() or "cdn" in check_desc.lower(): | |
| has_bootstrap = "bootstrap" in content.lower() | |
| return { | |
| "check": check_desc, | |
| "passed": has_bootstrap, | |
| "score": 1.0 if has_bootstrap else 0.0, | |
| "reason": f"Bootstrap {'found' if has_bootstrap else 'not found'} in page", | |
| } | |
| # Default: check if description keywords appear in content | |
| keywords = re.findall(r"\b\w{4,}\b", check_desc.lower()) | |
| matches = sum(1 for kw in keywords if kw in content.lower()) | |
| score = min(matches / max(len(keywords), 1), 1.0) | |
| return { | |
| "check": check_desc, | |
| "passed": score >= 0.5, | |
| "score": score, | |
| "reason": f"Matched {matches}/{len(keywords)} keywords", | |
| } | |