Автоматическое создание тестов Playwright из Jira с OpenAI GPT-5.1

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

Превращайте баг-репорты в воспроизводимые тест-кейсы — корректно, безопасно и с приложенными в Jira доказательствами.

Автоматическое создание тестов Playwright из Jira с OpenAI GPT-5.1

Резюме

В статье показано, как собрать полноценный продакшн-пайплайн, который:

  1. принимает вебхуки issue_created из Jira,
  2. использует OpenAI GPT-5.1 для парсинга и структурирования описания бага в детерминированные шаги теста,
  3. генерирует надёжные Playwright-тесты (включая рекомендации по селекторам),
  4. запускает тесты в изолированном раннере и собирает trace/video/logs,
  5. повторно использует GPT-5.1 для анализа падений и предложений по фиксам,
  6. отправляет результаты и артефакты обратно в задачу Jira.

GPT-5.1 здесь используется для генерации качественного кода и аккуратных JSON-структур. Модель доступна через OpenAI API и отлично справляется с задачами программного агента, что делает ее идеальной для подобного рабочего процесса.

Зачем вообще использовать LLM (GPT-5.1)?

  • LLM лучше всего переводят текстовые баг-репорты в структурированные действия (goto, click, fill), чего regex-подходы не обеспечивают.
  • GPT-5.1 повышает надежность генерации кода и добавляет инструменты, полезные для редактирования кода и взаимодействия с шеллом.

Тем не менее, мы не доверяем модели вслепую: здесь есть жесткие защитные механизмы — JSON-схемы, детерминированные проверки и возможность ручного ревью, чтобы исключить опасные выводы.

Высокоуровневая архитектура

  1. Jira → webhook → Оркестратор
  2. Оркестратор вызывает OpenAI (Parser prompt с выводом в формате структурированного JSON) → получает список шагов и метаданные
  3. Валидатор проверяет JSON (схема + эвристические проверки)
  4. Генератор тестов использует валидированный JSON, чтобы запросить у LLM Playwright-тест (или применяет шаблон + LLM-патчи)
  5. Playwright-раннер выполняет тест в изолированной среде → формирует видео/trace/logs
  6. Анализатор падений (LLM) читает trace/logs и предлагает фиксы (при необходимости — ручное ревью)
  7. Оркестратор отправляет результаты и вложения обратно в 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 применяется API responses.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 =&gt; 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-тестирование изменений и отслеживать версионирование.

Как это работает от начала до конца (когда появляется новый баг)

  1. QA заводит баг в Jira и четко прописывает блок “Шаги воспроизведения”.
  2. Вебхук вызывает /webhook/jira, оркестратор запускает callParser.
  3. GPT возвращает корректный JSON.
  4. Оркестратор вызывает generateTestWithModel для генерации Playwright-теста.
  5. Тест сохраняется и запускается в изолированной среде.
  6. Если тест падает, запускается анализатор ошибок, который формирует понятное объяснение и рекомендации по исправлению.
  7. Все артефакты (видео, трейс) прикрепляются к задаче в Jira.

Затраты и производительность

  • GPT-5.1 генерирует код заметно точнее, но обходится дороже, чем более ранние модели. Чтобы контролировать расходы и стабильность, задавайте ограничения max_tokens и используйте temperature: 0. Для повторяющихся баг-репортов можно кэшировать ранее разобранный JSON.

Ограничения системы на текущем этапе

  • Не справится с задачами, требующими глубокого знания домена: 2FA, CAPTCHA, аппаратные токены — нужны ваши API или тестовые аккаунты.
  • Не может идеально определять селекторы в сильно динамичных приложениях — всегда помечайте предполагаемые селекторы и проверяйте их.
  • Не может обходить корпоративные политики безопасности.

Проверка и метрики

Отслеживайте следующие показатели, чтобы оценить окупаемость:

  • Время воспроизведения бага (человек vs. AI-конвейер)
  • Долю тестов, которые работают без правок
  • Количество багов, закрытых быстрее благодаря прикрепленным артефактам
  • Стоимость одного запуска теста (вычислительные ресурсы + затраты на токены)

Ссылки и источники

Перевод статьи «Automated Jira → Playwright Test Creator (AI-powered with OpenAI GPT-5.1)».

🔥 Какой была ваша первая зарплата в QA и как вы искали первую работу? 

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Читать в телеграм

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *