Skip to main content

qa-verify

Automated QA verification for OpenShift Console PRs. Builds and runs the console on both main and the PR branch, captures before/after screenshots and GIFs via Playwright MCP, then posts a side-by-side comparison as a GitHub PR comment. Use when the user asks to verify a PR, QA changes, capture visual proof, or show before/after evidence.

Informações da origem

Repositório
openshift/console
Última atividade na origem
18 de setembro de 2026 às 21:13
Idioma detectado do SKILL.md
inglês
Estrelas
460
Forks
766

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
7 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
qa-verify
description
Automated QA verification for OpenShift Console PRs. Builds and runs the console on both main and the PR branch, captures before/after screenshots and GIFs via Playwright MCP, then posts a side-by-side comparison as a GitHub PR comment. Use when the user asks to verify a PR, QA changes, capture visual proof, or show before/after evidence.
argument-hint
[GitHub PR URL or number]
arguments
["pr"]
disable-model-invocation
true
effort
max
allowed-tools
["Bash(git *)","Bash(gh *)","Bash(go test *)","Bash(curl *)","Bash(mkdir *)","Bash(kill *)","Bash(bash *)","Bash(nohup *)","Bash(cp *)","Bash(python3 *)","Bash(sips *)","Bash(oc whoami)","Bash(./build-frontend.sh)","Bash(./build-backend.sh)","Bash(cat *)","Bash(sleep *)","Bash(date *)","Bash(find *)","Bash(wc *)","AskUserQuestion","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_wait_for","mcp__playwright__browser_resize","mcp__playwright__browser_close","mcp__playwright__browser_console_messages","mcp__playwright__browser_evaluate","mcp__atlassian__getJiraIssue"]
# /qa-verify Capture before/after visual evidence of PR changes using Playwright MCP and post a comparison to the GitHub PR. ## Current State Branch: !`git rev-parse --abbrev-ref HEAD` PR: !`gh pr view $pr --json number,title,body,headRefName,baseRefName 2>/dev/null || echo "No PR found"` Base branch: !`gh pr view $pr --json baseRefName -q '.baseRefName' 2>/dev/null || echo "main"` Commits since base: !`git log "$(gh pr view $pr --json baseRefName -q '.baseRefName' 2>/dev/null || echo main)..HEAD" --oneline --no-merges 2>/dev/null || echo "No commits ahead of base"` Changed files: !`git diff "$(gh pr view $pr --json baseRefName -q '.baseRefName' 2>/dev/null || echo main)...HEAD" --stat 2>/dev/null || echo "Unable to diff"` ## Phase 0: Prerequisites ```bash bash "${CLAUDE_SKILL_DIR}/scripts/check-prerequisites.sh" ``` If any tools are missing, the script outputs an `INSTALL_COMMANDS` section with platform-specific install commands (brew, dnf, or apt). Use `AskUserQuestion` to offer to run them. If the user agrees, run each install command, then re-run the prerequisites check to confirm everything passes. Verify Playwright MCP is available by calling `browser_snapshot`. If unavailable, the user needs to configure it **before starting this session** — adding MCP servers requires a session restart. Configure with `--ignore-https-errors` and `--save-video` for video recording: ```json { "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--ignore-https-errors", "--save-video=1920x1080"] } } } ``` The `--save-video=1920x1080` flag records a video of the entire browser session from page creation to `browser_close`. Videos are saved as `.webm` files automatically. ## Phase 1: Gather Context Use the injected state above. Additionally: Extract the Jira key from the PR title or branch name (`CONSOLE-\d+` or `OCPBUGS-\d+`). If Atlassian MCP is available, call `getJiraIssue` with `cloudId: "redhat.atlassian.net"`. From the PR body, parse: - **Test setup** section (between `**Test setup:**` and the next `**` header) - **Test cases** section (between `**Test cases:**` and the next `**` header) - **Solution description** section ## Phase 2: Verification Plan Derive verification steps and present to the user with `AskUserQuestion`. Wait for confirmation. Sources (use what's available): 1. PR test cases → each becomes a verification step with route, action, expected result 2. Jira ticket → bug repro steps or acceptance criteria map to pages 3. Diff → changed components map to affected routes 4. Fallback → dashboard and pages related to changed files 5. **Backend-only changes** — if the diff only touches `pkg/`, `cmd/`, or `*.go` files: - Check whether the fix depends on cluster resources (CRDs, operators) that may not exist in the local dev environment. If so, note this limitation in the plan. - Offer the user a choice: (a) run `go test ./pkg/...` on both branches and compare results instead of the full build-and-screenshot flow, or (b) proceed with UI verification but focus on functional behavior (button states, API responses) rather than pixel comparison. - If the user chooses test comparison, run the tests, diff the results, and skip Phases 3-4. **Route guidelines:** - Use namespace-scoped routes (`/k8s/ns/default/...`) instead of `/k8s/all-namespaces/...` for any page with forms or project selectors. With all-namespaces, no project is selected, so form validation independently disables submit buttons — making verification useless. - Pick a namespace that exists on the cluster (e.g., `default`, `openshift-console`). Present as a table: | # | Route | Action | Expected Result | |---|-------|--------|-----------------| | 1 | /dashboards | Navigate, wait load | Dashboard renders | | 2 | /k8s/ns/default/pods | Click a pod | Pod detail page opens | | 3 | /k8s/ns/openshift-console/deployments/console/environment | View env tab, click Add from ConfigMap | Value-from-pair form opens | The `$pr` argument (from `/qa-verify <url-or-number>`) is expanded by skill substitution into the content before you see it. If provided, it is used for `gh pr view` and `gh pr checkout`. It may be a full GitHub URL or just a number. **PR suitability:** This skill works best for PRs with visible UI changes (new components, layout changes, styling) where before/after screenshots show a clear diff. For logic-only PRs (state management, hooks, refactors), the skill still provides value as a regression check — identical screenshots confirm the refactor didn't break anything visually. In these cases, also check for console errors via `browser_console_messages` after each navigation step to catch runtime regressions that don't produce visible changes. **Data-loading pages:** Pages like `/k8s/cluster/projects` load data via WebSocket — the page may render before data arrives. Use `browser_wait_for` with `time: 5` (longer) for data-heavy pages, or poll for a specific element via `browser_evaluate`. For paginated lists, append `?perPage=200` to show all items, or use the filter/search input to find specific items instead of scrolling through pages. **Verification depth:** Err on the side of comprehensive testing over simplicity. Think about corner cases the PR might affect — empty states, error states, loading states, boundary conditions (e.g., very long names, special characters, zero items, maximum items). If the PR touches a component, verify it in multiple contexts where it appears, not just the most obvious one. A false negative (missing a regression) is worse than spending extra time on verification. ## Phase 3: Capture Evidence Run the verification plan twice: baseline (`${BASE_BRANCH}`) first, then candidate (PR branch). ### Setup ```bash REPO_ROOT=$(git rev-parse --show-toplevel) ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD) # $pr is expanded by skill substitution to the argument value (URL or number) PR_NUMBER=$(echo "$pr" | grep -oE '[0-9]+' | tail -1) if [ -z "$PR_NUMBER" ]; then PR_NUMBER=$(gh pr view --json number -q '.number' 2>/dev/null || echo "") fi PR_BRANCH=$(gh pr view --json headRefName -q '.headRefName' 2>/dev/null || echo "$ORIGINAL_BRANCH") # Derive baseline branch from PR target — release-4.x for backports, main otherwise BASE_BRANCH=$(gh pr view ${PR_NUMBER} --json baseRefName -q '.baseRefName' 2>/dev/null || echo "main") # Track whether the candidate branch already exists locally (for cleanup) PR_BRANCH_LOCAL=$(gh pr view ${PR_NUMBER} --json headRefName -q '.headRefName' 2>/dev/null || echo "") CANDIDATE_BRANCH_EXISTED=$(git rev-parse --verify "${PR_BRANCH_LOCAL}" >/dev/null 2>&1 && echo "yes" || echo "no") TIMESTAMP=$(date +%Y%m%d-%H%M%S) ARTIFACTS="${REPO_ROOT}/.artifacts/qa-verify/${TIMESTAMP}" mkdir -p "${ARTIFACTS}/baseline/screenshots" "${ARTIFACTS}/candidate/screenshots" # Evidence is posted via gh's native `--attach`. Check viability now, before doing any of the # expensive build/capture work, and stop immediately if evidence could never be posted. # # --attach needs write (push) access to the repo being commented on, which most contributors # won't have on an org repo like openshift/console. If so, fall back to STAGING: upload once # to a personal fork (which gh's --attach *can* push to) via a PR comment or scratch issue, # then reference the resulting hosted URLs in a plain (non-attach) comment on the target repo. # See stage-attachments.sh for why PRs are preferred over issues there. REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null || echo "") USE_ATTACH="no" STAGING_REPO="" if [ -n "$REPO" ]; then CAN_PUSH=$(gh api "repos/${REPO}" --jq '.permissions.push' 2>/dev/null || echo "false") if [ "$CAN_PUSH" = "true" ]; then USE_ATTACH="yes" else # A user's fork is not guaranteed to share the upstream repo's name (e.g. a fork of # openshift/console named "john" instead of "console"), search the user's own repos # for one whose *parent* matches. /user/repos returns the simple representation (parent # is always null there), so each fork candidate needs its own fetch to check .parent.full_name. while IFS= read -r fork_candidate; do [ -z "$fork_candidate" ] && continue INFO=$(gh api "repos/${fork_candidate}" --jq '(.parent.full_name // "") + "\t" + (.permissions.push|tostring)' 2>/dev/null || echo "") PARENT="${INFO%%$'\t'*}" CAN_PUSH_FORK="${INFO##*$'\t'}" if [ "$PARENT" = "$REPO" ] && [ "$CAN_PUSH_FORK" = "true" ]; then STAGING_REPO="$fork_candidate" break fi done < <(gh api user/repos --paginate --jq '.[] | select(.fork==true) | .full_name' 2>/dev/null || true) if [ -n "$STAGING_REPO" ]; then # Staging also needs an existing PR or Issues enabled on the fork — see # stage-attachments.sh's own check for the authoritative, up-to-date version of this. HAS_PR=$(gh pr list --repo "$STAGING_REPO" --state all --limit 1 --json number -q '.[0].number' 2>/dev/null || true) HAS_ISSUES=$(gh api "repos/${STAGING_REPO}" --jq '.has_issues' 2>/dev/null || echo "false") if [ -z "$HAS_PR" ] && [ "$HAS_ISSUES" != "true" ]; then echo "STOP: found fork ${STAGING_REPO} to stage uploads through, but it has no" >&2 echo "existing pull request and Issues are disabled. Fix one of these, then retry:" >&2 echo " gh api --method PATCH repos/${STAGING_REPO} -f has_issues=true" >&2 echo " # or open any PR on ${STAGING_REPO}" >&2 exit 1 fi fi fi fi if [ "$USE_ATTACH" = "no" ] && [ -z "$STAGING_REPO" ]; then echo "STOP: no write access to ${REPO:-<unknown repo>}, and no personal fork of it found to" >&2 echo "stage uploads through. Fork the repo, then retry. No PR comment will be posted." >&2 exit 1 fi ``` Use `PR_BRANCH` (the PR's actual head ref name) in metadata, not `ORIGINAL_BRANCH` (which is whatever local branch you happen to be on). Copy skill scripts to the artifacts directory so they survive branch switches (the scripts live on the PR branch and disappear when checking out the base branch): ```bash cp -R "${CLAUDE_SKILL_DIR}/scripts" "${ARTIFACTS}/scripts" chmod +x "${ARTIFACTS}/scripts/"*.sh ``` All subsequent script calls MUST use `${ARTIFACTS}/scripts/` instead of `${CLAUDE_SKILL_DIR}/scripts/`. ### For each lane (baseline first, then candidate): #### 3.1 Switch branch **Baseline:** ```bash STASH_COUNT_BEFORE=$(git stash list 2>/dev/null | wc -l | tr -d ' ') git stash --include-untracked -m "qa-verify-stash" 2>/dev/null || true STASH_COUNT_AFTER=$(git stash list 2>/dev/null | wc -l | tr -d ' ') QA_STASH_CREATED=$([ "$STASH_COUNT_AFTER" -gt "$STASH_COUNT_BEFORE" ] && echo "yes" || echo "no") git checkout "${BASE_BRANCH}" ``` **Candidate** (after baseline is complete): use `gh pr checkout` to handle forks correctly. External contributor PRs come from forks — `git checkout <branch>` will fail because the branch doesn't exist on origin. `gh pr checkout` fetches the correct ref automatically. ```bash gh pr checkout "${PR_NUMBER}" 2>/dev/null || git checkout "${ORIGINAL_BRANCH}" CANDIDATE_CHECKOUT_SHA=$(git rev-parse HEAD) if [ "${QA_STASH_CREATED}" = "yes" ]; then git stash pop 2>/dev/null || true fi ``` #### 3.2 Build Clean and rebuild frontend + backend in parallel (use `timeout: 600000`): ```bash bash "${ARTIFACTS}/scripts/rebuild.sh" ``` #### 3.3 Start server Replace `<LANE>` with `baseline` or `candidate`: ```bash bash "${ARTIFACTS}/scripts/backend.sh" --start <LANE> ``` #### 3.4 Navigate and capture Set viewport: `browser_resize` → width: 1920, height: 1080 For each verification step: 1. `browser_navigate` to `http://localhost:9000{route}` — note that the console may append query params (e.g., `?page=1&perPage=50`). This is normal and expected. 2. `browser_wait_for` with `time: 3` to let the page settle (do NOT use text matching — it frequently matches hidden elements and times out) 3. `browser_snapshot` for READ-ONLY verification of page state. Do NOT use snapshot refs as click targets — refs go stale instantly on pages with live data (CPU/memory counters, pod status). Instead, use `browser_evaluate` with `document.querySelector` for all interactions: ``` browser_evaluate: () => document.querySelector('[data-test="create-button"]').click() browser_evaluate: () => document.querySelector('input[aria-label="Filter"]').value = 'test' ``` 4. `browser_take_screenshot` → `${ARTIFACTS}/<LANE>/screenshots/NN-description.png` Use **identical filenames** for both lanes. **Checking element states:** To verify a button is enabled/disabled, use `browser_snapshot` with
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub