Как нейросеть ищет баги на скриншотах: анализ UI для QA
Playwright снимает экран, модель с поддержкой изображений возвращает JSON с опечатками, наложениями и пропавшими элементами. Код на Python, промпт и честный расчёт стоимости.
4 мин · · разработка · автоматизация
Ручная проверка интерфейса после каждого релиза отнимает время, а глаз быстро замыливается. Анализ скриншотов нейросетью закрывает часть этой работы: автотест снимает экран, модель смотрит на картинку и возвращает список подозрительных мест — опечатки, наложения, пропавшие кнопки. Ниже рабочий код на Python для Playwright, шаблон промпта и способ посчитать стоимость без догадок.
Какие модели умеют смотреть на картинки
Изображения принимают модели Claude и GPT. GLM 5.3 и GLM 5.3 Flash работают только с текстом — скриншот им отправлять бессмысленно.
| Модель | Вход / выход, $ за 1M токенов | Формат |
|---|---|---|
claude-sonnet-5 | 0.3 / 0.3 | Anthropic |
claude-opus-5-5 | 0.3 / 0.3 | Anthropic |
gpt-5.6-sol | 0.3 / 0.3 | OpenAI |
gpt-5.6-luna | 0.1 / 0.1 | OpenAI |
Набор моделей меняется, актуальный список — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Какая модель лучше находит баги именно в вашем интерфейсе, заранее сказать нельзя: прогоните одни и те же 10–20 скриншотов с заранее известными дефектами через две-три модели и сравните, сколько дефектов каждая нашла и сколько ложных срабатываний выдала.
Что модель замечает, а что нет
Нейросеть хорошо подходит как дополнительный слой проверки, но не заменяет попиксельное сравнение (visual regression) и ручное тестирование. Разумно поручать ей:
- опечатки и несогласованный текст в кнопках, заголовках, подсказках;
- явно перекрывающиеся или обрезанные элементы;
- пропавшие элементы, которые вы перечислили в промпте («на экране входа должны быть поле email, поле пароля и кнопка "Войти"»);
- плохо читаемый текст: слишком светлый на светлом фоне, слишком мелкий;
- несоответствие текста состоянию экрана («Загрузка…» при уже показанных данных).
Плохо работает всё, что требует точных измерений: «отступ 15 px вместо 16», «оттенок не совпадает с брендбуком на 3%». Для этого есть инструменты сравнения изображений.
Отправить скриншот через Anthropic SDK
Скриншот кодируется в base64 и передаётся блоком image. Для Anthropic SDK базовый адрес указывается без /v1 — SDK добавляет его сам.
import base64
from anthropic import Anthropic
client = Anthropic(base_url="https://api.zukko.pro", api_key="fetch_ваш_ключ")
with open("screenshot.png", "rb") as f:
image_data = base64.standard_b64encode(f.read()).decode("utf-8")
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": [
{"type": "image", "source": {
"type": "base64", "media_type": "image/png", "data": image_data}},
{"type": "text", "text": "Найди на скриншоте баги интерфейса. Ответь JSON."},
],
}],
)
print(message.content[0].text)
print(message.usage.input_tokens, message.usage.output_tokens)По документации Anthropic поддерживаются форматы PNG, JPEG, GIF и WebP, максимальный размер — 8000×8000 пикселей. Крупные изображения перед обработкой уменьшаются по длинной стороне, поэтому мелкий текст на огромном full-page скриншоте может стать нечитаемым.
Тот же запрос в формате OpenAI
Если в проекте уже используется OpenAI SDK, картинку передают как data URL в блоке image_url. Базовый адрес — с /v1:
import base64
from openai import OpenAI
client = OpenAI(base_url="https://api.zukko.pro/v1", api_key="fetch_ваш_ключ")
with open("screenshot.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
resp = client.chat.completions.create(
model="gpt-5.6-sol",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Найди на скриншоте баги интерфейса. Ответь JSON."},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
],
}],
)
print(resp.choices[0].message.content)Шлюз умеет конвертировать форматы, но перед запуском на сотнях экранов проверьте выбранную связку модель + формат на одном скриншоте.
Шаблон промпта для QA
Просьба «найди баги» даёт расплывчатый ответ. Перечислите критерии и ожидаемые элементы экрана, а формат ответа задайте жёстко:
QA_PROMPT = """Ты проверяешь скриншот веб-приложения перед релизом.
Экран: {screen}. На нём обязательно должны быть: {required}.
Ищи только реальные проблемы:
1. Опечатки и ошибки в тексте интерфейса.
2. Перекрывающиеся, обрезанные или вылезающие за край элементы.
3. Отсутствие обязательных элементов из списка выше.
4. Плохо читаемый текст (низкий контраст, слишком мелкий шрифт).
5. Текст, который противоречит состоянию экрана.
Если сомневаешься — не включай пункт. Ответь только JSON без пояснений:
{{"bugs": [{{"type": "typo|overlap|missing|contrast|state",
"location": "где на экране",
"description": "что не так",
"severity": "critical|minor"}}],
"status": "ok|warn|critical"}}"""Двойные фигурные скобки нужны, потому что строка заполняется через .format(). Модель иногда оборачивает JSON в блок кода, поэтому разбирайте ответ терпимо:
import json
import re
def parse_json(text: str):
match = re.search(r"\{.*\}", text, re.S)
if not match:
return None
try:
return json.loads(match.group(0))
except json.JSONDecodeError:
return NoneИнтеграция с Playwright
Скрипт обходит несколько страниц, снимает каждую и складывает находки в отчёт. Используется асинхронный клиент, чтобы не блокировать цикл событий Playwright.
import asyncio
import base64
import json
from anthropic import AsyncAnthropic
from playwright.async_api import async_playwright
SCREENS = [
("https://example.com/login", "вход", "поле email, поле пароля, кнопка «Войти»"),
("https://example.com/settings", "настройки", "кнопка «Сохранить»"),
]
async def main():
client = AsyncAnthropic(base_url="https://api.zukko.pro", api_key="fetch_ваш_ключ")
report = []Для мобильной вёрстки задайте узкий viewport (viewport={"width": 375, "height": 812}) или профиль устройства из p.devices. Длинные страницы лучше снимать несколькими экранами, а не одним full_page=True: так текст не потеряет читаемость после уменьшения.
Сколько стоит анализ скриншотов нейросетью
Число токенов на картинку зависит от её размера и модели, поэтому не стоит опираться на чужие оценки — измерьте на своих экранах. В ответе API есть usage.input_tokens (картинка + промпт) и usage.output_tokens. Для Anthropic-формата можно заранее вызвать POST /v1/messages/count_tokens с тем же содержимым.
Формула для claude-sonnet-5:
стоимость = (input_tokens × 0.3 + output_tokens × 0.3) / 1 000 000
Пример: если на вашем экране вышло 2 000 входных и 400 выходных токенов, то (2 000 × 0.3 + 400 × 0.3) / 1 000 000 = $0.00072 за скриншот, а прогон 300 экранов — 300 × 0.00072 = $0.216. Для gpt-5.6-luna ставьте в формулу 0.1 вместо 0.3. Скрипт выше уже сохраняет токены в отчёт, так что расход видно по каждому экрану.
Чтобы отдельный ключ для QA не съел бюджет, задайте ему дневной лимит и список разрешённых моделей в кабинете — подробнее в разделе лимиты ключей. Создать ключ можно на странице API-ключи, а параметры запроса с картинками описаны в справке по Messages API.
Частые вопросы
Можно ли отправлять скриншоты в GLM, чтобы сэкономить?
Нет. GLM 5.3 и GLM 5.3 Flash не принимают изображения. Самый дешёвый вариант с поддержкой картинок — gpt-5.6-luna за $0.1 за 1M токенов; проверьте на своих экранах, хватает ли её для ваших задач.
Что делать с конфиденциальными данными на скриншотах?
Запускайте проверку на тестовых данных и тестовых аккаунтах. Если реальные данные неизбежны, замаскируйте поля перед снимком: у page.screenshot() в Playwright есть параметр mask со списком локаторов.
Почему модель находит «баги», которых нет?
Так бывает, особенно при расплывчатом промпте. Помогают три вещи: список обязательных элементов экрана, инструкция «если сомневаешься — не включай» и ручная проверка находок с пометкой critical перед заведением задачи.
Отправлять страницу целиком или частями?
Обычный экран 1280×800 — целиком. Длинную страницу — несколькими снимками по высоте viewport, иначе после уменьшения мелкий текст может стать нечитаемым.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.