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",
        }