Spaces:
Sleeping
Sleeping
File size: 7,139 Bytes
c5292d8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | """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",
}
|