промпт
Добавь автотест на неуспешный логин с неправильным паролем
Выкл — агент этот файл не читает.
skill · SKILL.md
docs/agent-skills/qa-write-test/SKILL.md
# Разработай автотест
RAG (этот промпт, 4 id):
po-locators, po-step, test-negative, cfg-stands.
ADR: docs/adr/009-login-401-is-api.md
## When
«напиши e2e», «добавь negative login»
## Do not
- CSS/xpath в *Tests
- новый ChromeDriver в тесте
Commit / URL / -Denv — rule
401 vs форма — ADR 009
## Steps
1. Один @Layer
2. Стенды pipeline / stage / prod
3. Расширь PO, локаторы не в тесте
4. @Tag яруса + positive/negative
5. ./gradlew test -Denv=ci -DincludeTags=e2e \
-Dtest=LoginTests#<method>
## DoD
слой, стенды, @Step на PO, exit
как попадает: «прочитай SKILL.md»
When напиши e2e / покрой логин Don't CSS в *Tests, ChromeDriver RAG po-locators · po-step · test-negative · cfg-stands ADR 009 — не решай 401 в чате Шаги слой → стенды → PO → -Dtest= DoD слой, стенды, @Step на PO, exit
rule · .cursor/rules rule · .clinerules rule · AGENTS.md + .clinerules rule · AGENTS.md
.cursor/rules/01…04.mdc + AGENTS.md
# 01-qa-java-gradle.mdc
---
alwaysApply: false
globs: tests/java/.../**
---
./gradlew test -Denv=ci -DincludeTags=e2e \
-DexcludeTags=screenshot,mock
# task testE2e нет
# @Tag("smoke") — prod slice, не эта команда (ADR 005)
# 02-git-safety.mdc
---
alwaysApply: true
---
не commit / push / reset --hard без OK
# 03-env-and-stand.mdc
всегда -Denv=
URL не в Java
pipeline / stage / prod
# 04-one-task-one-layer.mdc
один task = один ярус
.clinerules/01…04.md + AGENTS.md
# 01-qa-java-gradle.md
---
paths: tests/java/.../**
---
./gradlew test -Denv=ci -DincludeTags=e2e \
-DexcludeTags=screenshot,mock
# task testE2e нет
# @Tag("smoke") — prod slice, не эта команда (ADR 005)
# 02-git-safety.md
не commit / push / reset --hard без OK
# 03-env-and-stand.md
всегда -Denv=
URL не в Java
pipeline / stage / prod
# 04-one-task-one-layer.md
один task = один ярус
AGENTS.md + .clinerules/01…04.md
# в takeaway нет CLAUDE.md / .claude/rules
# те же лимиты: AGENTS.md + .clinerules
# AGENTS.md
не commit / push / reset --hard без OK
URL из properties, всегда -Denv=
один task = один @Layer
pipeline / stage / prod
# .clinerules/01-qa-java-gradle.md
---
paths: tests/java/.../**
---
./gradlew test -Denv=ci -DincludeTags=e2e \
-DexcludeTags=screenshot,mock
# task testE2e нет
# @Tag("smoke") — prod slice, не эта команда (ADR 005)
# 02-git-safety.md
не commit / push / reset --hard без OK
# 03-env-and-stand.md
pipeline / stage / prod
# 04-one-task-one-layer.md
один task = один ярус
AGENTS.md · корень репо
# AGENTS.md
# QA Agent — takeaway Java/Selenide
## E2e
cd tests/java/tests-java-...-selenide
./gradlew test -Denv=ci -DincludeTags=e2e \
-DexcludeTags=screenshot,mock
# task testE2e нет
# @Tag("smoke") — prod slice, не эта команда (ADR 005)
## Ограничения
не commit / push / reset --hard без OK
URL из properties, не localhost в коде
один task = один @Layer
pipeline / stage / prod
как попадает: сам, если alwaysApply / globs как попадает: сам, если Rules ON как попадает: AGENTS.md + .clinerules (в репо нет CLAUDE.md) как попадает: сам, читает AGENTS.md
01 e2e = tags + -Denv, task testE2e нет 02 не commit / push без OK 03 URL только из properties, всегда -Denv 04 один task = один ярус
rag · po-locators · po-step · test-negative · cfg-stands
docs/agent-skills/rag/*.md
# po-locators
loginInput = $("[data-testid='login-input']")
errorMessage= $("[data-testid='error-message']")
# не $("input") в *Tests
# po-step
@Step на методах PO, не на классе
@Step("Type password") // не {password}
не Allure.step в *Tests + @Step
# test-negative
typeUsername().typePassword()
.submitExpectingError()
# не fillAndSubmitForm — ждёт HomePage
текст «Wrong login or password»
@Tag("negative")
# cfg-stands
URL из config
user1 есть на pipeline / stage / prod
как попадает: skill назвал эти 2–4 файла
data-testid только в LoginPage typeUsername / submitExpectingError @Step на методах PO, не на классе пароль не в тексте шага текст «Wrong login or password» стенды pipeline / stage / prod
adr · 009-login-401-is-api.md
docs/adr/009-login-401-is-api.md
# ADR 009: UI-текст e2e, 401 JSON — api
Статус: принято
Контекст: неверный пароль — форма и JSON.
В чате хочется второй e2e «на 401».
Решение:
- текст на форме →
LoginTests#shouldShowErrorWhenPasswordIsWrong
- 401 JSON → уже
AuthApiTests#loginWithInvalidPassword
- новый браузерный тест «на 401» не пишем
Последствия:
- не $("pre") «401»
- селекторы и текст ошибки — RAG, не ADR
как попадает: skill ссылается, сам не копирует
401 JSON → AuthApiTests#loginWithInvalidPassword
текст на форме → LoginTests e2e
не $("pre") «401»
test · LoginTests.java
tests/e2e/LoginTests.java · вкладка
@Layer("e2e")
@Epic("Authentication")
@Feature("Login")
class LoginTests extends TestBase {
@Test
@Tag("e2e")
@Tag("smoke")
@Tag("positive")
@DisplayName("User is logged in with valid credentials")
void shouldLoginWithValidCredentials() {
loginPage.openPage()
.fillAndSubmitForm("user1", "password1")
.shouldHaveWelcomeMessage("Welcome, user1!");
}
// shouldShowErrorWhenPasswordIsWrong
// на занятии комментируют —
// иначе skill скажет «не дублируй»
}
вкладка: успешный логин
@Layer("e2e")
@Epic("Authentication")
@Feature("Login")
class LoginTests extends TestBase {
@Test
@Tag("e2e")
@Tag("smoke")
@Tag("positive")
@DisplayName("User is logged in with valid credentials")
void shouldLoginWithValidCredentials() {
loginPage.openPage()
.fillAndSubmitForm("user1", "password1")
.shouldHaveWelcomeMessage("Welcome, user1!");
}
}
page · LoginPage.java
pages/LoginPage.java · вкладка
class LoginPage {
loginInput = $("[data-testid='login-input']");
passwordInput = $("[data-testid='password-input']");
submitButton = $("[data-testid='submit-button']");
@Step("Open login page")
LoginPage openPage() {
open("/login");
return this;
}
@Step("Fill and submit form")
HomePage fillAndSubmitForm(String username, String password) {
typeUsername(username).typePassword(password).submit();
return new HomePage();
}
@Step("Type username: {username}")
LoginPage typeUsername(String username) {
loginInput.setValue(username);
return this;
}
@Step("Type password")
LoginPage typePassword(String password) {
passwordInput.setValue(password);
return this;
}
}
вкладка: форма, happy path, @Step на методах
class LoginPage {
loginInput = $("[data-testid='login-input']");
passwordInput = $("[data-testid='password-input']");
submitButton = $("[data-testid='submit-button']");
@Step("Open login page")
LoginPage openPage() {
open("/login");
return this;
}
@Step("Fill and submit form")
HomePage fillAndSubmitForm(String username, String password) {
typeUsername(username).typePassword(password).submit();
return new HomePage();
}
@Step("Type username: {username}")
LoginPage typeUsername(String username) {
loginInput.setValue(username);
return this;
}
@Step("Type password")
LoginPage typePassword(String password) {
passwordInput.setValue(password);
return this;
}
}
page · HomePage.java
pages/HomePage.java · вкладка
class HomePage {
welcomeMessage = $("[data-testid='welcome-message']");
@Step("Verify welcome message: {message}")
HomePage shouldHaveWelcomeMessage(String message) {
welcomeMessage.shouldHave(text(message));
return this;
}
}
fillAndSubmitForm возвращает HomePage · @Step на методе
class HomePage {
welcomeMessage = $("[data-testid='welcome-message']");
@Step("Verify welcome message: {message}")
HomePage shouldHaveWelcomeMessage(String message) {
welcomeMessage.shouldHave(text(message));
return this;
}
}
base · TestBase.java
tests/TestBase.java
class TestBase {
protected LoginPage loginPage = new LoginPage();
protected static final TestConfig config =
ConfigReader.testConfig;
@BeforeAll
static void setup() {
Configuration.baseUrl = config.baseUrl();
Configuration.browser = config.browser();
Configuration.browserSize = config.browserSize();
Configuration.headless = config.headless();
}
// не new ChromeDriver() в тесте
}
вкладкой не открываем — видит сам
protected LoginPage loginPage = new LoginPage();
@BeforeAll
static void setup() {
Configuration.baseUrl = config.baseUrl();
Configuration.browser = config.browser();
Configuration.browserSize = config.browserSize();
Configuration.headless = config.headless();
}
// не new ChromeDriver() в тесте
props · ci.properties
config/ci.properties · -Denv=ci
baseUrl=http://localhost:9821/ browser=chrome browserVersion=148 browserSize=1920x1280 headless=true
не трогал — URL не в Java
baseUrl=http://localhost:9821/ browser=chrome browserVersion=148 browserSize=1920x1280 headless=true
build · build.gradle
tests/java/tests-java-gradle-junit5-allure3-selenide/build.gradle
javaVersion = 21
junitVersion = '5.11.4'
selenideVersion = '7.17.0'
allureVersion = '3.13.0'
ownerVersion = '1.0.12'
testImplementation (
"com.codeborne:selenide:${selenideVersion}",
"org.junit.jupiter:junit-jupiter:${junitVersion}",
"io.qameta.allure:allure-selenide",
"org.aeonbits.owner:owner:${ownerVersion}"
)
test {
useJUnitPlatform {
def includeTagsProp = System.getProperty('includeTags')
if (includeTagsProp) {
includeTags(*(includeTagsProp.split(',')*.trim()))
}
def excludeTagsProp = System.getProperty('excludeTags')
if (excludeTagsProp) {
excludeTags(*(excludeTagsProp.split(',')*.trim()))
}
}
systemProperty 'env', testEnv
}
вкладка: версии, -DincludeTags, -Denv=; task testE2e нет
javaVersion = 21
junitVersion = '5.11.4'
selenideVersion = '7.17.0'
allureVersion = '3.13.0'
ownerVersion = '1.0.12'
testImplementation (
"com.codeborne:selenide:${selenideVersion}",
"org.junit.jupiter:junit-jupiter:${junitVersion}",
"io.qameta.allure:allure-selenide",
"org.aeonbits.owner:owner:${ownerVersion}"
)
test {
useJUnitPlatform {
def includeTagsProp = System.getProperty('includeTags')
if (includeTagsProp) {
includeTags(*(includeTagsProp.split(',')*.trim()))
}
def excludeTagsProp = System.getProperty('excludeTags')
if (excludeTagsProp) {
excludeTags(*(excludeTagsProp.split(',')*.trim()))
}
}
systemProperty 'env', testEnv
}
cli · терминал
шелл · вкладкой не открываем
# агент гоняет сам, не третья вкладка cd tests/java/tests-java-gradle-junit5-allure3-selenide ./gradlew test -Denv=ci -DincludeTags=e2e \ -Dtest=LoginTests#shouldLoginWithValidCredentials # task testE2e нет
прогон из шелла, не Java
cd tests/java/tests-java-gradle-junit5-allure3-selenide ./gradlew test -Denv=ci -DincludeTags=e2e \ -Dtest=LoginTests#shouldLoginWithValidCredentials # task testE2e нет
ci · ci.yml
.github/workflows/ci.yml
# два флага: -Denv / -DincludeTags
# стенд и срез. task testE2e нет.
e2e-tests:
env:
LAYER: e2e
steps:
- uses: ./tests/.github/actions/e2e
with:
stand: prod
# внутри action:
# ./gradlew test -Denv=prod
# -DincludeTags=e2e
# -DexcludeTags=mock,screenshot
job вызывает action, не task testE2e
e2e-tests:
env:
LAYER: e2e
steps:
- uses: ./tests/.github/actions/e2e
with:
stand: prod
# ./gradlew test -Denv=prod
# -DincludeTags=e2e
# -DexcludeTags=mock,screenshot
# task testE2e нет