"""Модуль для работы с JSON-контейнерами на веб-страницах. Содержит компонент для чтения и проверки JSON-данных в контейнерах. Использует playwright для взаимодействия с элементами страницы. """ import json import re from typing import Any, Dict import jsondiff from playwright.sync_api import Page from tools.logger import get_logger from components.base_component import BaseComponent logger = get_logger("JSON_CONTAINER") class JsonContainerComponent(BaseComponent): """Компонент для работы с JSON-данными на странице. Предоставляет методы чтения и проверки JSON-данных в контейнерах. """ def __init__(self, page: Page) -> None: """Инициализирует JSON-контейнер. Args: page: Экземпляр страницы Playwright. """ self.page = page def format_json_string(self, json_string: str) -> str: """Форматирует строку JSON для корректного парсинга. Args: json_string: Сырая строка с JSON-данными. Returns: str: Отформатированная строка JSON. """ lines = json_string.splitlines() formatted_lines = [] stack = [] # Стек для отслеживания вложенности current_indent = 0 for line in lines: line = line.strip() if not line: continue # Определяем тип текущей строки if line in ['{', '[']: formatted_lines.append(' ' * current_indent + line) stack.append(line) current_indent += 1 elif line in ['}', ']']: current_indent -= 1 if stack and stack[-1] in ['{', '[']: stack.pop() formatted_lines.append(' ' * current_indent + line) elif re.match(r'^\d+:\{', line): formatted_lines.append(' ' * current_indent + '{') stack.append('{') current_indent += 1 elif ':' in line: key, value = line.split(':', 1) key = key.strip() value = value.strip() if not (key.startswith('"') and key.endswith('"')): key = f'"{key}"' if value in ['{', '[']: formatted_line = f'{key}: {value}' formatted_lines.append(' ' * current_indent + formatted_line) stack.append(value) current_indent += 1 elif value in ['}', ']']: current_indent -= 1 formatted_line = f'{key}: {value}' formatted_lines.append(' ' * current_indent + formatted_line) if stack: stack.pop() else: if (value and not value.isdigit() and not value.replace('.', '', 1).isdigit() and value not in ['true', 'false', 'null'] and not value.startswith('"') and not value.endswith('"') and not value.startswith('{') and not value.startswith('[')): value = f'"{value}"' formatted_line = f'{key}: {value}' formatted_lines.append(' ' * current_indent + formatted_line) else: formatted_lines.append(' ' * current_indent + line) # Добавляем запятые где необходимо result = [] total_lines = len(formatted_lines) for i, current_line in enumerate(formatted_lines): if i < total_lines - 1: next_line = formatted_lines[i + 1] in_array = any(bracket == '[' for bracket in stack) # Упрощенная проверка условий для запятой no_comma_condition1 = current_line.endswith(('{', '[', ',')) no_comma_condition2 = next_line.strip().endswith(('}', ']')) no_comma_condition3 = next_line.strip().startswith(('}', ']')) no_comma_condition4 = in_array and next_line.strip() == ']' should_add_comma = not (no_comma_condition1 or no_comma_condition2 or no_comma_condition3 or no_comma_condition4) # Специальный случай для элементов массива if (in_array and current_line.strip() == '}' and next_line.strip() != ']' and not next_line.strip().startswith('}')): should_add_comma = True if should_add_comma: current_line += ',' result.append(current_line) return '\n'.join(result) def read_data(self, locator: Any) -> Dict: """Читает и форматирует JSON-данные из указанного локатора. Args: locator: Локатор элемента с JSON-данными. Returns: dict: Распарсенный JSON-объект. Raises: json.JSONDecodeError: Если данные не могут быть преобразованы в JSON. """ loc = self.get_locator(locator) json_string = loc.inner_text() formatted_json_string = self.format_json_string(json_string) try: data = json.loads(formatted_json_string) except json.JSONDecodeError as e: logger.error("JSON decode error: %s", e) logger.error("Formatted JSON: %s", formatted_json_string) assert False, f"Invalid json content. Error: {e}" return data def check_json_equals(self, actual: Any, expected: Any, msg: str) -> None: """Сравнивает JSON-объекты на идентичность. Args: actual: Фактический JSON-объект. expected: Ожидаемый JSON-объект. msg: Сообщение об ошибке. Raises: AssertionError: Если объекты не идентичны. """ diff = jsondiff.diff(expected, actual, syntax='symmetric') assert len(diff) == 0, f"{msg}. DIFF is {diff}"