<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Katja</title>
    <description>The latest articles on DEV Community by Katja (@quality_minder).</description>
    <link>https://dev.to/quality_minder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4143124%2Fff12ad70-c7e8-4c33-ada7-696ec770d030.jpg</url>
      <title>DEV Community: Katja</title>
      <link>https://dev.to/quality_minder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/quality_minder"/>
    <language>en</language>
    <item>
      <title>Building an AI QA Agent That Tests Your Jira Tickets in a Real Browser</title>
      <dc:creator>Katja</dc:creator>
      <pubDate>Fri, 25 Sep 2026 17:05:32 +0000</pubDate>
      <link>https://dev.to/quality_minder/building-an-ai-qa-agent-that-tests-your-jira-tickets-in-a-real-browser-3pc7</link>
      <guid>https://dev.to/quality_minder/building-an-ai-qa-agent-that-tests-your-jira-tickets-in-a-real-browser-3pc7</guid>
      <description>&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Give it a Jira ticket ID and a test environment URL, and this agent reads the ticket, writes test steps, and runs them in a real Chrome window while you watch.&lt;/p&gt;

&lt;p&gt;It's built from two open-source pieces. &lt;strong&gt;Agent Factory&lt;/strong&gt; is the studio where agents are designed, edited, and chatted with. &lt;strong&gt;BaaS&lt;/strong&gt; (Browser as a Service) is the part that actually drives the browser: it opens pages, clicks, waits, and takes screenshots on the agent's behalf.&lt;/p&gt;

&lt;p&gt;This post walks through setting both up locally and running the bug check agent against your own app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;Required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A GitHub account&lt;/li&gt;
&lt;li&gt;An OpenAI API key (Anthropic works too, but I recommend starting with OpenAI)&lt;/li&gt;
&lt;li&gt;Google Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recommended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker.&lt;/strong&gt; The instructions below assume it, but a bare-metal setup works as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;, so you can run BaaS locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Start Agent Factory
&lt;/h2&gt;

&lt;p&gt;Clone the repo: &lt;a href="https://github.com/Ursa-Minor-Beta/agent-factory-docker-api-ui" rel="noopener noreferrer"&gt;Ursa-Minor-Beta/agent-factory-docker-api-ui&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then create your env file and bring everything up with the bundled database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# edit .env – at minimum set the admin email and password&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;--profile&lt;/span&gt; with-db up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The admin email and password in &lt;code&gt;.env&lt;/code&gt; are what you'll use to log in to the factory later, so pick something you'll remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Run BaaS locally
&lt;/h2&gt;

&lt;p&gt;I recommend running BaaS locally while you debug and in Docker while you test, because locally you can watch exactly what the agent does in the browser. If the local setup fights you (OS permissions and the like), fall back to Docker. It's stable.&lt;/p&gt;

&lt;p&gt;Start MongoDB and create the env file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;baas
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; mongodb
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
vim .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set these values in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;pick-a-key&amp;gt;

&lt;span class="nv"&gt;BROWSER_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'&lt;/span&gt;
&lt;span class="nv"&gt;BROWSER_HEADFUL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true

&lt;/span&gt;&lt;span class="nv"&gt;LLM_CLIENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openai
&lt;span class="nv"&gt;OPENAI_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your token here&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_ORGANIZATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your organization here&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BROWSER_HEADFUL=true&lt;/code&gt; is what makes the Chrome window visible. The path above is for macOS; point it at your own Chrome binary on other systems. Remember your &lt;code&gt;API_KEY&lt;/code&gt;, because the studio needs it in Step 3.&lt;/p&gt;

&lt;p&gt;Next, map &lt;code&gt;host.docker.internal&lt;/code&gt; to localhost so the Dockerized factory and the local BaaS can find each other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/hosts
&lt;span class="c"&gt;# add this line:&lt;/span&gt;
127.0.0.1       host.docker.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, load the env and start BaaS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; ./.env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; +a
go run ./cmd/baas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see warnings about missing Pandoc, PDF-to-image, and wmctrl. Ignore them: you don't need those tools, and this is what a successful start looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Connect the studio and run a smoke test
&lt;/h2&gt;

&lt;p&gt;Open the studio at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; and log in with the admin credentials from Step 1. Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Secrets&lt;/strong&gt; and add &lt;code&gt;BAAS_API_KEY&lt;/code&gt;, set to the same value as &lt;code&gt;API_KEY&lt;/code&gt; in the BaaS &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Provider&lt;/strong&gt; and add your OpenAI (or Anthropic) API key.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the whole setup. To check the browser actually works, go to &lt;strong&gt;Agents → Browser Screenshot&lt;/strong&gt; and enter &lt;code&gt;https://google.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A new Chrome window will open. It may ask for permissions, so allow them. If everything is wired up, the window loads Google and the chat returns a browser session ID plus a screenshot of the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Wire up Jira and the Test Orchestrator
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a Jira auth secret
&lt;/h3&gt;

&lt;p&gt;Log in to &lt;a href="https://id.atlassian.com/manage-profile/security/api-tokens" rel="noopener noreferrer"&gt;Atlassian API tokens&lt;/a&gt; and choose &lt;strong&gt;Create API token with scopes&lt;/strong&gt;. Then encode your email and token together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"you@example.com:your-api-token"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the output to the studio's &lt;strong&gt;Secrets&lt;/strong&gt; under the name &lt;code&gt;Jira_auth&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the orchestrator
&lt;/h3&gt;

&lt;p&gt;In the factory, go to &lt;strong&gt;Agents&lt;/strong&gt;, find &lt;strong&gt;Test Orchestrator&lt;/strong&gt;, and click &lt;strong&gt;Edit&lt;/strong&gt;. Two things to change:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;input-1&lt;/code&gt; node, replace the default URL (&lt;code&gt;https://google.com&lt;/code&gt;) with your test environment URL, and set your &lt;code&gt;jiraSubdomain&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Decide how to handle login in the &lt;code&gt;http-1&lt;/code&gt; node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For login, I recommend hardcoding it in &lt;code&gt;http-1&lt;/code&gt;. Login flows rarely change, so scripting them once saves tokens on every run.&lt;/p&gt;

&lt;p&gt;If you'd rather skip a separate login step, set the body of &lt;code&gt;http-1&lt;/code&gt; to the following. It starts a browser session, opens the URL, and takes a screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;program&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;navigate('{{node:input-1.url}}'); sleep('3s'); waitReady(body,'timeout:20s'); takeScreenshot('screenshot', 'timeout:10s');&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sessionID&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{node:http-0.response.result.sessionID}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;stopSession&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: false}"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Run your first test, then tune it
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Test Orchestrator&lt;/strong&gt; and click &lt;strong&gt;Chat&lt;/strong&gt;. Send it your Jira ticket ID (something like &lt;code&gt;SCRUM-165&lt;/code&gt;) and your test environment URL. The agent takes it from there and runs the first test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Teach it about your app
&lt;/h3&gt;

&lt;p&gt;The prompt in &lt;code&gt;llm-generate-steps&lt;/code&gt; is where test steps get written. Replace the example details there with what you know about your application. Pasting in your user documentation, if you have any, works well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tune the Test Step Executor
&lt;/h3&gt;

&lt;p&gt;The orchestrator uses &lt;strong&gt;Test Step Executor&lt;/strong&gt; as a skill, and its prompts are worth adapting to your app and its quirks. Each LLM node has one job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llm-0&lt;/code&gt;&lt;/strong&gt; adapts each test step to what's actually on screen, using the screenshot. Tell it about any layout or navigation it should expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llm-1&lt;/code&gt;&lt;/strong&gt; writes the BaaS commands. Tell it which selectors to prefer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llm-2&lt;/code&gt;&lt;/strong&gt; decides whether a step passed, failed, or is broken. Tell it anything to check closely, like whether the logo is visible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;I hope this agent saves you some manual testing. If you run into problems or have ideas, get in touch.&lt;/p&gt;

&lt;p&gt;One honest ask: this is a side project, and I'd like to know whether to keep building it. If it's useful to you, &lt;a href="https://github.com/Ursa-Minor-Beta/agent-factory-docker-api-ui" rel="noopener noreferrer"&gt;star the repo on GitHub&lt;/a&gt;, send me a DM, or leave a comment below. Any of those tells me the time is well spent.&lt;/p&gt;

&lt;p&gt;Keep humans on the loop and don’t ship on Fridays!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>automation</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
