Skip to content

Instantly share code, notes, and snippets.

@purplenimbus
Last active June 9, 2026 19:12
Show Gist options
  • Select an option

  • Save purplenimbus/099e558b32c91f51e9a762e0209efc55 to your computer and use it in GitHub Desktop.

Select an option

Save purplenimbus/099e558b32c91f51e9a762e0209efc55 to your computer and use it in GitHub Desktop.
qa-bot: Automated QA for Frontend PRs (Hackathon 2026)
description Run automated QA on a frontend PR. Executes QA steps from the PR description against localhost using Playwright, then posts PASS/FAIL results with screenshots back to the PR. Use when asked to QA a PR or run qa-bot.

qa-bot: Automated QA for Frontend PRs

Step 1: Get the PR

If a PR number or URL was passed as an argument, use it. Otherwise, ask the user:

Which PR would you like to QA? (provide a PR number or URL)

Resolve the PR details with:

gh pr view <number_or_url> --json number,url,title,headRefName,body

Step 2: Extract QA steps

Parse the ## QA Steps section from the PR body. Each filled-in line item (e.g. - [ ] ... or - ...) is one step. Ignore unfilled template placeholders like [Add QA steps here].

If no filled-in QA steps are found, fall back in this order:

Fallback 1 β€” Linear ticket: Check the PR title and description for a Linear ticket ID (e.g. [MON-170], Closes MON-170). If found, fetch the ticket:

  • Use the Linear MCP get_issue tool to retrieve the ticket title and description
  • Infer a QA plan from the ticket's description and the PR title
  • Present the inferred steps to the user and ask for confirmation before proceeding:

    I couldn't find QA steps in the PR. Based on Linear ticket MON-170, here's what I plan to test:

    1. ...
    2. ... Does this look right, or would you like to adjust?

Fallback 2 β€” Prompt the user: If there's no Linear ticket, or the ticket description is too vague to infer steps, ask:

This PR doesn't have QA steps and I couldn't find a clear Linear ticket to infer from. Please describe what you'd like me to test.

Step 3: Clarify the steps (Woon's skill)

TODO: Once Woon's skill is named and its interface is defined, call it here, passing the raw QA steps and getting back clarified, Playwright-executable steps.

For now, use the raw steps as-is.

Step 4: Check localhost and server health

Branch assumption: The user is expected to already be on the correct branch before invoking qa-bot. Exception: if the PR branch has been merged and deleted from remote, run from main β€” the feature will already be present. Do not stash, checkout, or pull branches automatically unless the user explicitly asks.

Check localhost is running: Navigate to http://localhost:3001 and confirm the app responds. If it doesn't respond:

  • Run pnpm dev in the back-office repo directory (run in background)
  • Poll http://localhost:3001 until it responds (up to 60 seconds)
  • If still not responding after 60s, tell the user and stop.

Check for pending server migrations:

cd /Users/anthony/dev/server && mix ecto.migrations 2>&1 | grep "down"

If any migrations are listed as down, stop and prompt the user:

There are pending database migrations on the server. Please run mix ecto.migrate in the server repo before QA can proceed.

Step 5: Record and execute each QA step

Before running the steps, start a video recording using Playwright's recordVideo option:

  1. Pre-create the recording directory via Bash: mkdir -p /tmp/qa-bot-recording
  2. Use browser_run_code_unsafe to create a new browser context with recording enabled:
async (page) => {
  const browser = page.context().browser();
  const ctx = await browser.newContext({
    recordVideo: { dir: '/tmp/qa-bot-recording', size: { width: 1280, height: 800 } },
    viewport: { width: 1280, height: 800 }
  });
  const p = await ctx.newPage();
  // ... login, then run all QA steps ...
  const video = p.video();
  await ctx.close();
  return { videoPath: video ? await video.path() : null };
}

Login in the new context: navigate to /account/signin, fill input[type=email] and input[type=password], click button[type=submit], then waitForURL('**/users**').

For each clarified step:

  1. Execute the action in the recorded context
  2. Mark the step as PASS or FAIL with a one-line reason

If a step fails, continue to the next step (don't abort the whole run).

Step 6: Upload recording and post results to the PR

Upload the recording to a GitHub release asset:

gh release create "qa-bot-$(date +%s)" --repo <owner>/<repo> --title "qa-bot recording" --notes "" --draft <video_path>#qa-recording.webm
# Get the download URL:
gh release view <tag> --repo <owner>/<repo> --json assets --jq '.assets[-1].url'

Post a comment to the PR:

gh pr comment <number> --body "<results>"

Format the comment as:

## πŸ€– qa-bot Results βœ…/❌

| # | Step | Status | Notes |
|---|------|--------|-------|
| 1 | Navigate to /transactions and verify table loads | βœ… PASS | Table rendered with 5 rows |
| 2 | Click into a transaction and confirm Details tab | ❌ FAIL | Details tab not found in snapshot |

🎬 [QA recording](<release_asset_url>)

_Run `/qa-bot` to re-check after fixes._

The overall header emoji is βœ… if all steps pass, ❌ if any fail.

Step 7: Post results to the team's Slack PR channel

Look up the PR author's GitHub team to find their Slack channel:

# Find which engineering team the PR author belongs to
for team in execution foundation trading growth customer-accounts issuers investing money; do
  gh api orgs/hiivemarkets/teams/$team/memberships/<author_login> 2>/dev/null | grep -q '"state":"active"' && echo $team && break
done

Map the team slug to the Slack channel using the #team-dope-<slug>-prs pattern. Check it exists:

# Use Slack MCP slack_search_channels with query "team-dope-<slug>-prs" to confirm the channel ID

Only post to Slack if ALL of the following are true:

  • The PR branch is still open (not merged)
  • All QA steps passed

If any step failed, or the PR is already merged: skip the Slack post silently.

If the channel is not found: skip the Slack post silently. Do not fall back to any other channel.

Credentials (local)

  • URL: http://localhost:3001
  • Login via /account/signin
  • Email: anthony+sa@hiive.com
  • Password: Letmeintoh11ve

Key Playwright notes

  • After browser_navigate, use browser_wait_for with a visible string before interacting
  • Use browser_snapshot to understand page state before asserting PASS/FAIL
  • For Chakra <Select> elements, use browser_evaluate with the React native setter IIFE trick (see smoke-test-create-transaction skill)
  • browser_select_option is unreliable β€” avoid it
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment