Spaces:
Sleeping
Sleeping
| """ | |
| DOM Collector - снимки состояния страницы | |
| """ | |
| import time | |
| from typing import Dict, List | |
| from .base import BaseCollector | |
| class DOMCollector(BaseCollector): | |
| """ | |
| Собирает снимки состояния DOM. | |
| Записывает: | |
| - URL, title | |
| - Формы и их поля | |
| - Кнопки (особенно с data-testid) | |
| - Сообщения об ошибках | |
| - Индикаторы загрузки | |
| """ | |
| name = "dom" | |
| def __init__(self, session): | |
| super().__init__(session) | |
| self._snapshots = [] | |
| def inject(self): | |
| """DOM не требует инжекта""" | |
| self.log("DOM monitoring enabled") | |
| def collect(self) -> Dict: | |
| """Собирает снимок DOM""" | |
| return self.snapshot() | |
| def snapshot(self, label: str = "") -> Dict: | |
| """ | |
| Делает полный снимок состояния страницы. | |
| Args: | |
| label: Метка для снимка | |
| Returns: | |
| Словарь с данными о странице | |
| """ | |
| if not self.page: | |
| return {} | |
| try: | |
| data = self.page.run_js(''' | |
| return { | |
| url: location.href, | |
| title: document.title, | |
| // Текст страницы (первые 3000 символов) | |
| bodyText: document.body ? document.body.innerText.substring(0, 3000) : '', | |
| // Формы | |
| forms: Array.from(document.forms).slice(0, 10).map(f => ({ | |
| id: f.id || '', | |
| name: f.name || '', | |
| action: f.action || '', | |
| method: f.method || 'GET', | |
| inputs: Array.from(f.elements).slice(0, 30).map(e => ({ | |
| tag: e.tagName, | |
| name: e.name || e.id || '', | |
| type: e.type || '', | |
| placeholder: e.placeholder || '', | |
| value: e.type === 'password' ? '***' : (e.value || '').substring(0, 100), | |
| disabled: e.disabled, | |
| required: e.required, | |
| testId: e.getAttribute('data-testid') || '' | |
| })) | |
| })), | |
| // Кнопки | |
| buttons: Array.from(document.querySelectorAll('button, input[type="submit"], [role="button"], a[class*="button"]')) | |
| .slice(0, 30).map(b => ({ | |
| tag: b.tagName, | |
| text: (b.textContent || b.value || '').trim().substring(0, 100), | |
| type: b.type || '', | |
| disabled: b.disabled || false, | |
| visible: b.offsetParent !== null, | |
| testId: b.getAttribute('data-testid') || '', | |
| className: (b.className || '').substring(0, 100), | |
| href: b.href || '' | |
| })), | |
| // Ошибки | |
| errors: Array.from(document.querySelectorAll( | |
| '[class*="error"], [class*="Error"], .alert-danger, [role="alert"], ' + | |
| '[class*="invalid"], [class*="warning"], .error-message' | |
| )).slice(0, 10).map(e => ({ | |
| text: e.textContent.trim().substring(0, 300), | |
| className: (e.className || '').substring(0, 100), | |
| visible: e.offsetParent !== null | |
| })), | |
| // Индикаторы загрузки | |
| loadingIndicators: Array.from(document.querySelectorAll( | |
| '.loading, .spinner, [class*="loading"], [class*="spinner"], ' + | |
| '[class*="progress"], .awsui-spinner' | |
| )).slice(0, 5).map(e => ({ | |
| className: (e.className || '').substring(0, 100), | |
| visible: e.offsetParent !== null | |
| })), | |
| // Скрытые поля (могут содержать токены) | |
| hiddenInputs: Array.from(document.querySelectorAll('input[type="hidden"]')) | |
| .slice(0, 20).map(i => ({ | |
| name: i.name || '', | |
| value: i.value.substring(0, 200) | |
| })), | |
| // Мета-информация | |
| meta: { | |
| hasRedirecting: (document.body ? document.body.innerText : '').toLowerCase().includes('redirect'), | |
| hasLoading: !!document.querySelector('.loading, .spinner, [class*="loading"]'), | |
| hasError: !!document.querySelector('[class*="error"], [role="alert"]'), | |
| documentReady: document.readyState, | |
| scrollY: window.scrollY, | |
| innerHeight: window.innerHeight, | |
| innerWidth: window.innerWidth | |
| } | |
| }; | |
| ''') or {} | |
| # Добавляем метаданные | |
| data['timestamp'] = self.session._elapsed() | |
| data['label'] = label | |
| # Сохраняем | |
| self._snapshots.append(data) | |
| if label: | |
| self.log(f"Snapshot '{label}': {data.get('title', '')[:30]} | {len(data.get('forms', []))} forms | {len(data.get('buttons', []))} buttons") | |
| # Проверяем важные состояния | |
| meta = data.get('meta', {}) | |
| if meta.get('hasRedirecting'): | |
| self.log("Page shows 'Redirecting...'") | |
| if meta.get('hasError'): | |
| errors = data.get('errors', []) | |
| if errors: | |
| self.log(f"Error on page: {errors[0].get('text', '')[:50]}...") | |
| return data | |
| except Exception as e: | |
| self.log(f"Snapshot failed: {e}") | |
| return {} | |
| def on_step_end(self, step): | |
| """Добавляем снимки к шагу""" | |
| step.dom_snapshots = self._snapshots.copy() | |
| self._snapshots = [] | |