🔥 Важное для QA-специалистов! 🔥
В QaRocks ты найдешь туториалы, задачи и полезные книги, которых нет в открытом доступе. Уже более 17.000 подписчиков – будь среди нас! Заходи к нам в телеграм канал QaRocks
Превращайте баг-репорты в воспроизводимые тест-кейсы — корректно, безопасно и с приложенными в Jira доказательствами.

Резюме
В статье показано, как собрать полноценный продакшн-пайплайн, который:
- принимает вебхуки
issue_createdиз Jira, - использует OpenAI GPT-5.1 для парсинга и структурирования описания бага в детерминированные шаги теста,
- генерирует надёжные Playwright-тесты (включая рекомендации по селекторам),
- запускает тесты в изолированном раннере и собирает trace/video/logs,
- повторно использует GPT-5.1 для анализа падений и предложений по фиксам,
- отправляет результаты и артефакты обратно в задачу Jira.
GPT-5.1 здесь используется для генерации качественного кода и аккуратных JSON-структур. Модель доступна через OpenAI API и отлично справляется с задачами программного агента, что делает ее идеальной для подобного рабочего процесса.
Зачем вообще использовать LLM (GPT-5.1)?
- LLM лучше всего переводят текстовые баг-репорты в структурированные действия (goto, click, fill), чего regex-подходы не обеспечивают.
- GPT-5.1 повышает надежность генерации кода и добавляет инструменты, полезные для редактирования кода и взаимодействия с шеллом.
Тем не менее, мы не доверяем модели вслепую: здесь есть жесткие защитные механизмы — JSON-схемы, детерминированные проверки и возможность ручного ревью, чтобы исключить опасные выводы.
Высокоуровневая архитектура
- Jira → webhook → Оркестратор
- Оркестратор вызывает OpenAI (Parser prompt с выводом в формате структурированного JSON) → получает список шагов и метаданные
- Валидатор проверяет JSON (схема + эвристические проверки)
- Генератор тестов использует валидированный JSON, чтобы запросить у LLM Playwright-тест (или применяет шаблон + LLM-патчи)
- Playwright-раннер выполняет тест в изолированной среде → формирует видео/trace/logs
- Анализатор падений (LLM) читает trace/logs и предлагает фиксы (при необходимости — ручное ревью)
- Оркестратор отправляет результаты и вложения обратно в Jira
Безопасность и жесткие ограничения
- Только выбранные проекты в Jira могут инициировать запуск пайплайна.
- В описании обязательно должен быть блок
Steps to reproduce. Если его нет, LLM выдает запрос на уточнение, а не пишет тест. - Ответы — строго в формате JSON по утвержденной схеме; все, что не валидируется, автоматически отклоняется.
- Полный запрет на shell-команды: любые rm, curl, eval в ответе модели блокируются.
- Тесты выполняются в полностью изолированных контейнерах с ограниченной сетью и доступом к данным.
- Добавление тестов в прод или создание PR — только с участием человека.
Промпт-дизайн (фундамент стабильности)
У нас есть два основных промпта:
1) Parser prompt — превращает текст Jira в структурированный JSON
Мы просим GPT вывести СТРОГО следующий JSON по схеме:
{
"title": "short title",
"intent": "one-line intent",
"actions": [
{
"type": "goto|click|fill|press|expect|wait",
"value": "https://... or 'Login' or 'john@example.com'",
"selector": "optional CSS/xpath/text selector or null",
"timeout_ms": 5000
}
],
"assumptions": ["one-line assumption A", "assumption B"],
"confidence": 0.0
}js
Основные инструкции для модели:
- Если какой-либо шаг вызывает сомнения, ставить
selector: nullи добавлять короткое пояснение вassumptions, чтобы указать, что репортер может уточнить. - Значение
confidenceустанавливать в диапазоне от 0.0 до 1.0 (оценка модели). - Возвращать только JSON — без дополнительных пояснений.
2) Промпт для генерации теста — формирование Playwright-файла
Входные данные: валидированный JSON + домен сайта + тестовые логины (при наличии).
Инструкция: создать Playwright-тест в стиле кода, принятом в репозитории (JS или TS). Все ненадёжные или предполагаемые селекторы должны быть помечены // TODO, а ключевые проверки — реализованы через expect. Если в JSON селектор null, просто оставляем // TODO.
Совет: можно попросить GPT добавить в начало файла блок метаданных с generated_by: gpt-5.1 и checksum списка шагов для удобства трассировки.
Примеры промптов (copy/paste)
Parser prompt (укороченный вариант):
You are a safe parser. Input is a Jira issue description. Output ONLY valid JSON matching this schema: { "title": "...", "intent":"...", "actions":[{ "type": "goto|click|fill|press|expect|wait", "value":"...", "selector": null | "...", "timeout_ms":5000 }], "assumptions":[...], "confidence":0.0 }
Rules:
- Extract "Steps to reproduce:" numbered list first.
- Map lines like "Open https://..." -> type: "goto".
- Map "Click 'Login'" -> type: "click", set selector to 'text="Login"' when safe.
- If any line is ambiguous, set selector:null and explain in assumptions.
- Output only JSON, nothing else.
Test template prompt (короткий):
Given the following validated JSON: <PASTE_JSON>, produce a Playwright test file (CommonJS) that implements the actions. - Use page.goto, page.click, page.fill, page.waitForTimeout, and expect(locator).toBeVisible() where applicable. - Mark guess-work with // TODO comments. - Prepend a metadata comment with generated_by, timestamp, checksum. Return just the full test file content.
Важно: почему подход “JSON-first” + шаблон валидатора?
Требование к модели выдавать корректный JSON значительно уменьшает риск галлюцинаций и позволяет валидировать ответ до запуска кода. Если структура JSON не проходит проверку, оркестратор запрашивает повтор или передаёт задачу на ручную проверку.
Готовый к запуску код (Node.js)
Поместите файлы в любую папку и выполните:
npm init -y && npm i openai express axios dotenv form-data playwright ajv crypto, затемnpx playwright install. Подставьте свои env-переменные и запустите приложение.Примечание: в примерах используется официальный OpenAI JS SDK (
openai), который вызывает эндпоинт Responses / Chat для GPT-5.1. Подробности — в документации OpenAI.
.env (пример)
PORT=3000 JIRA_BASE=https://your-domain.atlassian.net JIRA_EMAIL=your@email.com JIRA_API_TOKEN=your-jira-token OPENAI_API_KEY=sk-... PLAYWRIGHT_HEADFUL=false ARTIFACTS_DIR=./artifacts ALLOWED_JIRA_PROJECTS=PROJ1,PROJ2
server.js — оркестратор (обработчик вебхуков)
// server.js
import express from "express";
import bodyParser from "body-parser";
import dotenv from "dotenv";
import fs from "fs/promises";
import path from "path";
import { callParser } from "./ai-client.js";
import { validateParsed } from "./validator.js";
import { generateTestWithModel } from "./test-ai-generator.js";
import { runPlaywrightTest } from "./runner.js";
import { attachToJira, commentOnJira } from "./jira-client.js";
dotenv.config();
const PORT = process.env.PORT || 3000;
const ARTIFACTS = process.env.ARTIFACTS_DIR || "./artifacts";
await fs.mkdir(ARTIFACTS, { recursive: true });
const app = express();
app.use(bodyParser.json({ limit: "2mb" }));
app.post("/webhook/jira", async (req, res) => {
try {
const payload = req.body;
const issue = payload.issue;
const projectKey = issue.fields.project?.key;
const allowed = (process.env.ALLOWED_JIRA_PROJECTS||"").split(",").map(s=>s.trim());
if (!allowed.includes(projectKey)) {
return res.status(403).send("Project not allowed");
}
const issueKey = issue.key;
const description = issue.fields.description || "";
// Step 1: parse using OpenAI
const parseResp = await callParser({ issueKey, description });
// Step 2: validate JSON schema
const { valid, errors } = validateParsed(parseResp);
if (!valid) {
await commentOnJira(issueKey, `❗ Unable to parse steps reliably. Errors: ${JSON.stringify(errors)}`);
return res.status(200).send({ ok: false, reason: "parse_invalid", errors });
}
// Step 3: generate Playwright test (ask model to produce test code)
const testCode = await generateTestWithModel(parseResp, { issueKey });
// write test file
const filePath = path.join(ARTIFACTS, `${issueKey}.spec.js`);
await fs.writeFile(filePath, testCode, "utf8");
// Step 4: run Playwright in sandbox
const runResult = await runPlaywrightTest({ testFilePath: filePath, issueKey, artifactsDir: ARTIFACTS });
// Step 5: analyze failures using model (optional)
if (!runResult.passed) {
// Ask LLM to analyze logs & suggest fixes
const analysis = await import("./failure-analyzer.js").then(m => m.analyzeFailure(runResult));
await commentOnJira(issueKey, `❌ Test failed. Analysis:\n\n${analysis.summary}`);
} else {
await commentOnJira(issueKey, `✅ Test passed. Artifacts attached.`);
}
// Attach artifacts
for (const f of runResult.artifacts || []) {
await attachToJira(issueKey, f);
}
return res.status(200).send({ ok: true });
} catch (err) {
console.error(err);
return res.status(500).send("server error");
}
});
app.listen(PORT, () => console.log(`Listening ${PORT}`));
ai-client.js — модуль для работы с OpenAI (парсинг и генерация)
// ai-client.js
import OpenAI from "openai";
import dotenv from "dotenv";
dotenv.config();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Parser call: ask GPT-5.1 to return strict JSON
export async function callParser({ issueKey, description }) {
const system = `You are a JSON-only parser. Output EXACTLY valid JSON (no commentary). Follow the schema:
{
"title":"", "intent":"", "actions":[{"type":"goto|click|fill|press|expect|wait","value":"", "selector":null,"timeout_ms":5000}], "assumptions":[], "confidence":0.0
}
If you can't parse a step, set selector:null and add assumptions explaining.
`;
const user = `Jira Issue ${issueKey} description:\n\n${description}\n\nExtract "Steps to reproduce" and map to actions.`;
const resp = await client.chat.completions.create({
model: "gpt-5.1-chat-latest",
messages: [
{ role: "system", content: system },
{ role: "user", content: user }
],
max_tokens: 900,
temperature: 0.0
});
// the SDK returns a structured choice -> extract text
const text = resp.choices?.[0]?.message?.content;
try {
return JSON.parse(text);
} catch (err) {
throw new Error("Parser returned non-JSON: " + text.slice(0,500));
}
}
Примечание: в примере используется вызов
chat.completions.create. Если в вашей версии SDK OpenAI применяется APIresponses.create, адаптируйте код соответствующим образом. Точные названия методов для вашей версии SDK см. в документации OpenAI.
validator.js — валидатор JSON-схемы на базе Ajv
// validator.js
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const schema = {
type: "object",
required: ["title","actions"],
properties: {
title: { type: "string" },
intent: { type: "string" },
actions: {
type: "array",
items: {
type: "object",
required: ["type","value","timeout_ms"],
properties: {
type: { type: "string", enum: ["goto","click","fill","press","expect","wait"] },
value: { type: "string" },
selector: { anyOf: [{type:"string"}, {type:"null"}] },
timeout_ms: { type: "integer" }
}
}
},
assumptions: { type: "array" },
confidence: { type: "number" }
}
};
const validate = ajv.compile(schema);
export function validateParsed(json) {
const valid = validate(json);
return { valid, errors: validate.errors };
}
test-ai-generator.js — безопасная генерация теста Playwright на основе проверенного JSON
// test-ai-generator.js
import OpenAI from "openai";
import dotenv from "dotenv";
dotenv.config();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function generateTestWithModel(parsedJson, { issueKey }) {
const system = `You are a Playwright test generator. Output ONLY a single Playwright CommonJS test file as plain text.
Do NOT include shell commands, secrets, or inline credentials. If 'selector' is null for any action, place a // TODO comment and use text selector as fallback. Add metadata comment at top with generated_by and checksum.`;
const user = `Issue: ${issueKey}\nParsed JSON:\n${JSON.stringify(parsedJson, null, 2)}\nGenerate the test.`;
const resp = await client.chat.completions.create({
model: "gpt-5.1-chat-latest",
messages: [
{ role: "system", content: system },
{ role: "user", content: user }
],
temperature: 0.0,
max_tokens: 1500
});
return resp.choices[0].message.content;
}
failure-analyzer.js — быстрый запуск LLM для объяснения падения теста и предложений по исправлению
// failure-analyzer.js<br>import OpenAI from "openai";<br>import dotenv from "dotenv";<br>import fs from "fs/promises";<br>dotenv.config();<br>const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });<br><br>export async function analyzeFailure(runResult) {<br> const stdout = runResult.stdout || "";<br> const artifactsList = (runResult.artifacts || []).map(p => p.split("/").pop()).join("\n");<br> const prompt = `You are a failure analysis assistant. Here is Playwright stdout:\n\n${stdout}\n\nArtifacts:\n${artifactsList}\n\nProvide a short summary (3 lines) of likely causes and 3 concrete remediation suggestions (selectors to try, waits to add, or test account needs).`;<br><br> const resp = await client.chat.completions.create({<br> model: "gpt-5.1-chat-latest",<br> messages: [{ role: "user", content: prompt }],<br> max_tokens: 400,<br> temperature: 0.0<br> });<br><br> return { summary: resp.choices[0].message.content };<br>}
runner.js — исполнитель Playwright-тестов (безопасный, запуск отдельных файлов)
// failure-analyzer.js
import OpenAI from "openai";
import dotenv from "dotenv";
import fs from "fs/promises";
dotenv.config();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function analyzeFailure(runResult) {
const stdout = runResult.stdout || "";
const artifactsList = (runResult.artifacts || []).map(p => p.split("/").pop()).join("\n");
const prompt = `You are a failure analysis assistant. Here is Playwright stdout:\n\n${stdout}\n\nArtifacts:\n${artifactsList}\n\nProvide a short summary (3 lines) of likely causes and 3 concrete remediation suggestions (selectors to try, waits to add, or test account needs).`;
const resp = await client.chat.completions.create({
model: "gpt-5.1-chat-latest",
messages: [{ role: "user", content: prompt }],
max_tokens: 400,
temperature: 0.0
});
return { summary: resp.choices[0].message.content };
}
jira-client.js — прикрепление файлов и комментариев (аналогично предыдущему примеру)
Используется та же реализация, что и для детерминированной версии, но оставлено здесь для полноты. (Прикрепление через X-Atlassian-Token: no-check и Basic Auth.).
Развертывание и инфраструктура
- Используйте очередь задач (BullMQ/Redis), чтобы ограничивать скорость выполнения и сохранять задания вебхуков.
- Запускайте Playwright-тесты во временных Docker-контейнерах с минимальным исходящим доступом.
- Регулярно ротируйте ключи OpenAI и отслеживайте использование и расходы. GPT-5.1 мощная модель, поэтому ограничивайте
max_tokensи применяйтеtemperature: 0для детерминированных результатов.
Шаблоны промптов (можно копировать)
Компактные версии промптов приведены выше; храните их в репозитории централизованно (например, prompts/parser.txt, prompts/generator.txt), чтобы иметь возможность проводить A/B-тестирование изменений и отслеживать версионирование.
Как это работает от начала до конца (когда появляется новый баг)
- QA заводит баг в Jira и четко прописывает блок “Шаги воспроизведения”.
- Вебхук вызывает
/webhook/jira, оркестратор запускаетcallParser. - GPT возвращает корректный JSON.
- Оркестратор вызывает
generateTestWithModelдля генерации Playwright-теста. - Тест сохраняется и запускается в изолированной среде.
- Если тест падает, запускается анализатор ошибок, который формирует понятное объяснение и рекомендации по исправлению.
- Все артефакты (видео, трейс) прикрепляются к задаче в Jira.
Затраты и производительность
- GPT-5.1 генерирует код заметно точнее, но обходится дороже, чем более ранние модели. Чтобы контролировать расходы и стабильность, задавайте ограничения
max_tokensи используйтеtemperature: 0. Для повторяющихся баг-репортов можно кэшировать ранее разобранный JSON.
Ограничения системы на текущем этапе
- Не справится с задачами, требующими глубокого знания домена: 2FA, CAPTCHA, аппаратные токены — нужны ваши API или тестовые аккаунты.
- Не может идеально определять селекторы в сильно динамичных приложениях — всегда помечайте предполагаемые селекторы и проверяйте их.
- Не может обходить корпоративные политики безопасности.
Проверка и метрики
Отслеживайте следующие показатели, чтобы оценить окупаемость:
- Время воспроизведения бага (человек vs. AI-конвейер)
- Долю тестов, которые работают без правок
- Количество багов, закрытых быстрее благодаря прикрепленным артефактам
- Стоимость одного запуска теста (вычислительные ресурсы + затраты на токены)
Ссылки и источники
- Документация модели GPT-5.1 и руководство для разработчиков.
- OpenAI Quickstart и API-справочник (chat/completions, responses).
- Руководство по prompt-инженерингу для GPT-5.1 (включая инструменты и функцию apply_patch).
Перевод статьи «Automated Jira → Playwright Test Creator (AI-powered with OpenAI GPT-5.1)».