<?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: Weiwen Weng</title>
    <description>The latest articles on DEV Community by Weiwen Weng (@weiwen-weng).</description>
    <link>https://dev.to/weiwen-weng</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%2F4009979%2Fd9e7e568-9bd0-427e-9df4-8f218d7f3e06.png</url>
      <title>DEV Community: Weiwen Weng</title>
      <link>https://dev.to/weiwen-weng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weiwen-weng"/>
    <language>en</language>
    <item>
      <title>Why YAML Tickets Beat Jira MCP for AI IDEs Like Cursor</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:14:29 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/why-yaml-tickets-beat-jira-mcp-for-ai-ides-like-cursor-38c</link>
      <guid>https://dev.to/weiwen-weng/why-yaml-tickets-beat-jira-mcp-for-ai-ides-like-cursor-38c</guid>
      <description>&lt;p&gt;Your AI IDE already reads the repo. If tickets are plain-text YAML next to the code, the agent can search the backlog the same way it reads a module — &lt;strong&gt;no MCP, no JQL translator, no cloud hop for every ask&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MCP into Jira is the bolt-on path. At a few thousand tickets — or around &lt;strong&gt;10,000&lt;/strong&gt; — the gap shows up in latency, hit rate, and junk in the context window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native path: tickets as YAML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.gitoza/tasks/tickets/{project}/{ticket-id}.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;ticket id is the filename&lt;/strong&gt; (without &lt;code&gt;.yaml&lt;/code&gt;). With stable keys (&lt;code&gt;status&lt;/code&gt;, &lt;code&gt;assignee&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;), an agent can answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Open bugs in auth assigned to me&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;What is still open on this release?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Which tickets mention the checkout API?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retrieval is local — IDE index, &lt;code&gt;grep&lt;/code&gt;, or a SQLite-backed app index. You only pay network time for the model, not for fetching every ticket over REST.&lt;/p&gt;

&lt;p&gt;Minimal example (&lt;code&gt;AUTH-102.yaml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lockout after failed logins&lt;/span&gt;
&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;medium&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bug&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Lockout after failed logins&lt;/span&gt;

&lt;span class="c1"&gt;## Description&lt;/span&gt;

&lt;span class="s"&gt;Brief context for the agent and the team.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point the agent at &lt;code&gt;.gitoza/tasks/tickets/&lt;/code&gt; and ask. Same loop as code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same ask over Jira MCP
&lt;/h2&gt;

&lt;p&gt;Useful when Atlassian must stay the system of record. For day-to-day agent work it adds a chain on every answer: natural language → tool call → JQL → REST → JSON → maybe another call if the first page was wrong.&lt;/p&gt;

&lt;p&gt;Fine for one known issue key. Heavy for wide or multi-step backlog questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed, precision, cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;YAML in the repo&lt;/th&gt;
&lt;th&gt;Jira over MCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI access&lt;/td&gt;
&lt;td&gt;Native (files the IDE already sees)&lt;/td&gt;
&lt;td&gt;Tools + network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Disk / local index&lt;/td&gt;
&lt;td&gt;Network + JQL + paging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrow asks&lt;/td&gt;
&lt;td&gt;High when keys are consistent&lt;/td&gt;
&lt;td&gt;Depends on JQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;td&gt;Ticket text you chose&lt;/td&gt;
&lt;td&gt;Schemas + JSON bloat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quotas&lt;/td&gt;
&lt;td&gt;None for local reads&lt;/td&gt;
&lt;td&gt;Cloud / MCP caps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;td&gt;Needs the cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Precision caveat:&lt;/strong&gt; “summarize all 10k tickets” still will not fit one context window. Filter first, then summarize — locally that is search/&lt;code&gt;grep&lt;/code&gt;, not “hope the JQL was right.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost direction:&lt;/strong&gt; local plain text is cheaper per useful answer than tool-shaped round trips. Many checks never need an LLM — a terminal search is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip the bridge
&lt;/h2&gt;

&lt;p&gt;If the team already works in an AI IDE, put the backlog where the agent already looks: the repo. Files need no tool schema, no JQL translation, and no second system for “what is open this sprint.”&lt;/p&gt;

&lt;p&gt;MCP is a workaround for a backlog trapped in a vendor API. YAML tickets remove that trap — Git is the data plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gitoza.com" rel="noopener noreferrer"&gt;Gitoza&lt;/a&gt; keeps tickets under &lt;code&gt;.gitoza/tasks/tickets/{project}/&lt;/code&gt; (releases beside them; tests under &lt;code&gt;.gitoza/test/&lt;/code&gt; if you want coverage next to the work). Browse in the desktop UI, or open the YAML in Cursor / VS Code and ask.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>yaml</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Manage Tickets in Git as YAML — No Login, No Jira MCP, Works Offline</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Sat, 08 Aug 2026 07:46:09 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/manage-tickets-in-git-as-yaml-no-login-no-jira-mcp-works-offline-4j1n</link>
      <guid>https://dev.to/weiwen-weng/manage-tickets-in-git-as-yaml-no-login-no-jira-mcp-works-offline-4j1n</guid>
      <description>&lt;p&gt;Driving tickets through MCP into Jira sounds neat until auth, scopes, rate limits, and field mappings eat the day. The IDE still cannot see the backlog unless that glue is perfect.&lt;/p&gt;

&lt;p&gt;There is a simpler pattern — closer to Obsidian than to another SaaS hop: &lt;strong&gt;keep tickets as YAML files in the repo&lt;/strong&gt;, then link them locally. No login. No vendor API for every lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tickets as files
&lt;/h2&gt;

&lt;p&gt;Put tickets under something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.gitoza/tasks/tickets/{project}/{ticket-id}.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Releases under &lt;code&gt;.gitoza/tasks/releases/&lt;/code&gt;. Links are just paths and IDs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ticket ↔ ticket (parent/child, &lt;code&gt;[[TICKET-ID]]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Ticket ↔ release&lt;/li&gt;
&lt;li&gt;Ticket ↔ test case in &lt;code&gt;.gitoza/test/cases/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open the file, follow the link, done. Structured enough for search and agents — not another cloud board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this wins
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Offline by default.&lt;/strong&gt; Source of truth is on disk. Searching a release or following a link does not wait on VPN or a flaky API. Sync to a remote when you want to share; editing does not require that round-trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI already speaks files.&lt;/strong&gt; Agents in Cursor / VS Code read YAML well. Ask what is open this sprint, which tickets lack cases, what sits on a release — without standing up MCP to a tracker first. Prefer a local model? Same repo, same folders, no ticket payload leaving your machine for a hosted “AI” bolted onto a SaaS board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One tree for planning and tests&lt;/strong&gt; (optional):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tickets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gitoza/tasks/tickets/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Releases&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gitoza/tasks/releases/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cases / runs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.gitoza/test/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gitoza.com" rel="noopener noreferrer"&gt;Gitoza&lt;/a&gt; is built around that layout — browse in Tickets / Releases views, or open the YAML in the IDE.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://gitoza.com/#download" rel="noopener noreferrer"&gt;Download&lt;/a&gt;, connect a repo (or Playground).&lt;/li&gt;
&lt;li&gt;Add YAML tickets, link a release.&lt;/li&gt;
&lt;li&gt;Point your IDE agent at &lt;code&gt;.gitoza/tasks/&lt;/code&gt; and ask something you would have sent through MCP to Jira.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the pitch: tickets as files, Obsidian-style links, local speed, AI without a brittle cloud bridge — and &lt;strong&gt;no login&lt;/strong&gt; to start.&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>yaml</category>
      <category>ai</category>
    </item>
    <item>
      <title>YAML Test Cases in Git: What r/QualityAssurance Actually Said</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Sun, 02 Aug 2026 16:10:08 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/yaml-test-cases-in-git-what-rqualityassurance-actually-said-1kc5</link>
      <guid>https://dev.to/weiwen-weng/yaml-test-cases-in-git-what-rqualityassurance-actually-said-1kc5</guid>
      <description>&lt;p&gt;I asked &lt;a href="https://www.reddit.com/r/QualityAssurance/comments/1v47gei/managing_test_cases_as_yaml_files_inside_git_a/" rel="noopener noreferrer"&gt;r/QualityAssurance&lt;/a&gt; a blunt question: is managing manual test cases as YAML files inside Git a good idea?&lt;/p&gt;

&lt;p&gt;The thread got lively. People were not hostile to the idea. Many were already sympathetic to &lt;strong&gt;tests as code&lt;/strong&gt;, a dedicated branch, AI helpers that can actually read the catalog. The pushback was more interesting than the praise. It lined up with problems I had hit building the same workflow for real teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the room agreed
&lt;/h2&gt;

&lt;p&gt;When you frame cases as plain text in a repo you control, the reaction is usually positive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No vendor database as the system of record&lt;/li&gt;
&lt;li&gt;Diffs and history that look like application code&lt;/li&gt;
&lt;li&gt;Collaboration through Git instead of another sync product&lt;/li&gt;
&lt;li&gt;AI tools (Cursor, Copilot, and the rest) that already live in the IDE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That part is not controversial anymore. Engineers have watched automation live in Git for years. Manual cases still often sit in a SaaS form or a spreadsheet. Closing that gap feels overdue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where people who tried it got burned
&lt;/h2&gt;

&lt;p&gt;Agreement on the &lt;em&gt;idea&lt;/em&gt; is not the same as a durable practice.&lt;/p&gt;

&lt;p&gt;Commenters who had lived with &lt;strong&gt;XML dumps, folder trees, or home-grown markdown catalogs&lt;/strong&gt; were blunt: without tooling, the suite becomes hard to browse, easy to break, and painful to keep alive. Raw files scale poorly for day-to-day authoring. Someone always ends up maintaining a fragile convention that nobody else wants to touch.&lt;/p&gt;

&lt;p&gt;Others were clear they would &lt;strong&gt;stay on web tools like Xray&lt;/strong&gt; (and the Jira orbit around them). Not because YAML is wrong — because managers get dashboards, links, and process they already trust. "Files in Git" sounds like an engineer hobby until it feels like a product.&lt;/p&gt;

&lt;p&gt;And then the &lt;strong&gt;traceability&lt;/strong&gt; question: how do you keep cases tied to user stories and bug tickets when you leave the vendor UI? Without a stable id story, file-based QA looks incomplete next to a TMS that speaks Jira natively.&lt;/p&gt;

&lt;p&gt;Those are fair objections. Ignoring them is how "test cases in Git" dies after a pilot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing piece was never Git — it was the UI
&lt;/h2&gt;

&lt;p&gt;Plenty of teams have tried putting cases in the repo. Almost nobody ships a &lt;strong&gt;local UI that renders and manages those files&lt;/strong&gt; the way a TMS renders rows in a database.&lt;/p&gt;

&lt;p&gt;That gap is the whole story.&lt;/p&gt;

&lt;p&gt;Reading YAML in a text editor is not a test management workflow. Browsing suites, editing steps, recording Pass / Fail / Skip, and seeing automation coverage — that is. Once you have a client that treats the files as the source of truth, the day-to-day experience stops being "weird Git experiment" and starts looking like the web tools people already know. In practice, stability and feature coverage land in the same ballpark. Latency often wins: everything is on disk. There is no round trip to a hosted API every time you open a case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runs that do not rewrite history when cases change
&lt;/h2&gt;

&lt;p&gt;File-based catalogs usually fail at &lt;strong&gt;execution history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You create a run, add cases, execute. Two weeks later someone edits step 3 on a case that was already in that run. If the run only stores a live path, yesterday's sign-off silently becomes today's wording. Auditors hate that. So do release leads.&lt;/p&gt;

&lt;p&gt;Git already has the answer if you use it deliberately. When Gitoza adds cases to a run, it records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;case path&lt;/strong&gt; in the repository&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;commit SHA&lt;/strong&gt; at the moment the case was picked (&lt;code&gt;picked_at_sha&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results attach to that membership. Later edits to the live case file do not rewrite what was executed. You can still open the catalog at HEAD for new work; historical runs resolve content against the SHA they were pinned to. Versioning is not a separate "export a PDF" step — it is how the run is stored.&lt;/p&gt;

&lt;p&gt;That is the difference between "we keep YAML in Git" and "runs are evidence."&lt;/p&gt;

&lt;h2&gt;
  
  
  Traceability without living inside Jira
&lt;/h2&gt;

&lt;p&gt;Vendor TMS tools win on &lt;strong&gt;links&lt;/strong&gt;: story → cases → bugs. File-based setups need the same discipline, just as fields.&lt;/p&gt;

&lt;p&gt;In Gitoza case YAML, that usually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requirement_id&lt;/code&gt; for the user story or requirement key&lt;/li&gt;
&lt;li&gt;tags for product area / smoke / risk&lt;/li&gt;
&lt;li&gt;optional custom keys under &lt;code&gt;params&lt;/code&gt; (for example &lt;code&gt;jira_key&lt;/code&gt;) when your tracker needs a second handle&lt;/li&gt;
&lt;li&gt;for automation, the case filename as the id, mirrored as a tag on Playwright or Robot specs — see &lt;a href="https://gitoza.com/blog/ai-link-manual-automated-test-cases" rel="noopener noreferrer"&gt;linking manual and automated cases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not get magic bi-directional sync with Jira out of a text file. You get stable ids you can filter, export, and review in a PR — and you keep the catalog next to the code those stories shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managers and testers need different surfaces — same data
&lt;/h2&gt;

&lt;p&gt;This is the split the Reddit thread kept circling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QA managers&lt;/strong&gt; lean on Xray, Jira, and similar products because reporting and process live there. That convenience is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testers who write cases all day&lt;/strong&gt; pay for it in browser forms, slow saves, and UIs that were never designed for long-form authoring. Asking a tester to draft dozens of careful cases in a web TMS is a bit like asking a programmer to write production code in a ticket comment box. Possible. Not the job you want.&lt;/p&gt;

&lt;p&gt;Gitoza's bet is simple: keep the &lt;strong&gt;data plane&lt;/strong&gt; in Git (YAML cases and runs you own), and give testers an &lt;strong&gt;IDE-shaped client&lt;/strong&gt; — browse, edit, run, sync — while managers still get review, dashboards, and history without renting the catalog to a vendor database.&lt;/p&gt;

&lt;p&gt;If your org will never leave Jira Test Management, stay there. If your pain is authoring speed, ownership, AI access, and audit trails that look like commits, file-based cases with a real UI are no longer a science project.&lt;/p&gt;

&lt;p&gt;Try it, poke holes in it&lt;/p&gt;

&lt;p&gt;The Desktop app and the VS Code extension are free to start; there is no lock-in beyond files in your own repo. Install Lite from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Marketplace&lt;/a&gt;, open a workspace, create a project under &lt;code&gt;.gitoza-lite/test/cases/&lt;/code&gt;, and see whether the workflow holds up on a real suite.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>git</category>
      <category>yaml</category>
    </item>
    <item>
      <title>Link Manual Test Cases to Playwright and Robot Specs</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Wed, 22 Jul 2026 15:44:09 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/link-manual-yaml-cases-to-playwright-and-robot-specs-5782</link>
      <guid>https://dev.to/weiwen-weng/link-manual-yaml-cases-to-playwright-and-robot-specs-5782</guid>
      <description>&lt;p&gt;Most teams already keep Playwright or Robot Framework specs next to the product. Manual cases often live somewhere else — a spreadsheet, a cloud TMS, or a wiki page nobody updates after the first release.&lt;/p&gt;

&lt;p&gt;That is why automation coverage is usually a quarterly guess: the catalog and the specs never share an id.&lt;/p&gt;

&lt;p&gt;Put both in the &lt;strong&gt;same Git repo&lt;/strong&gt;, give every manual case a stable id, and linking becomes a text match. Any IDE agent can do that if the cases are files it can already see — no custom AI product inside a test tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  One repo, two layers of the same suite
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual cases&lt;/strong&gt; are YAML files under &lt;code&gt;.gitoza-lite/test/cases/&lt;/code&gt; (VS Code / Cursor extension) or &lt;code&gt;.gitoza/test/cases/&lt;/code&gt; (Desktop app). The &lt;strong&gt;filename without &lt;code&gt;.yaml&lt;/code&gt; is the case id&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated tests&lt;/strong&gt; live wherever your team already puts them — &lt;code&gt;tests/&lt;/code&gt;, &lt;code&gt;e2e/&lt;/code&gt;, &lt;code&gt;robot/&lt;/code&gt; — in the same repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceability&lt;/strong&gt; is a shared id: put that case id on the automation side as a tag (or name), and on the YAML side as &lt;code&gt;automated: true&lt;/code&gt; plus a &lt;code&gt;params&lt;/code&gt; pointer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A PR can change the case, the Playwright spec, and the link in one review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Draft manual cases in the IDE
&lt;/h2&gt;

&lt;p&gt;Open the repo in Cursor or VS Code. Point the agent at a few existing case files so it learns your title style, tags, and step length. Then feed it a user story, a ticket, or a screenshot.&lt;/p&gt;

&lt;p&gt;Ask for several cases at once, not one shallow step. A useful prompt looks like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read &lt;code&gt;.gitoza-lite/test/cases/shopflow/auth/&lt;/code&gt; for format. From ticket SHOP-184, draft three YAML cases (happy path, invalid password, locked account). Filename = case id. Use tags &lt;code&gt;auth&lt;/code&gt; and &lt;code&gt;smoke&lt;/code&gt; where it fits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save the files under the suite folder. Then open the &lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Gitoza Lite&lt;/a&gt; Test Repository tab to browse, edit, and — when you are ready — run them as a manual suite with Pass / Fail / Skip.&lt;/p&gt;

&lt;p&gt;The extension does not ship its own model. It keeps cases as plain YAML so whatever assistant you already use can read and write them.&lt;/p&gt;

&lt;p&gt;A minimal case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login with valid credentials&lt;/span&gt;
&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;smoke&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## Steps&lt;/span&gt;
&lt;span class="s"&gt;1. Open the login page&lt;/span&gt;
&lt;span class="s"&gt;2. Enter valid credentials&lt;/span&gt;

&lt;span class="c1"&gt;## Expected result&lt;/span&gt;
&lt;span class="s"&gt;User is redirected to the dashboard.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file is &lt;code&gt;AUTH-001.yaml&lt;/code&gt;, the case id is &lt;code&gt;AUTH-001&lt;/code&gt;. That string is the join key for everything below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Put the case id on the automation side
&lt;/h2&gt;

&lt;p&gt;Before coverage can be marked, automation needs a handle that matches the YAML filename.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; — tags must start with &lt;code&gt;@&lt;/code&gt; (or include the id in the test title):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login with valid credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@AUTH-001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@smoke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Robot Framework&lt;/strong&gt; — use &lt;code&gt;[Tags]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight robot_framework"&gt;&lt;code&gt;&lt;span class="gh"&gt;*** Test Cases ***&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;Login With Valid Credentials&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;AUTH-001&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;smoke&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nf"&gt;Open Login Page&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nf"&gt;Submit Valid Credentials&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nf"&gt;Dashboard Should Be Visible&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea for other runners: one stable token that equals the case id. Prefer the id over a free-text title match — titles drift; filenames should not. When matching Playwright tags to YAML, strip the leading &lt;code&gt;@&lt;/code&gt; so &lt;code&gt;@AUTH-001&lt;/code&gt; maps to &lt;code&gt;AUTH-001.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Flip &lt;code&gt;automated&lt;/code&gt; and fill &lt;code&gt;params&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once specs carry the case id, close the loop on the YAML side — with Cursor or a small script:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scan &lt;code&gt;tests/&lt;/code&gt; for Playwright tags matching case ids under &lt;code&gt;.gitoza-lite/test/cases/&lt;/code&gt;. For each hit, set &lt;code&gt;automated: true&lt;/code&gt; on the YAML case and add &lt;code&gt;params.playwright&lt;/code&gt; with the relative spec path. If a case id appears in tags but not in YAML, list the gaps. Do not set &lt;code&gt;automated: true&lt;/code&gt; unless the tag and filename match after stripping &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You end up with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login with valid credentials&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;smoke&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;playwright&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tests/auth/login.spec.ts&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## Steps&lt;/span&gt;
&lt;span class="s"&gt;1. Open the login page&lt;/span&gt;
&lt;span class="s"&gt;2. Enter valid credentials&lt;/span&gt;

&lt;span class="c1"&gt;## Expected result&lt;/span&gt;
&lt;span class="s"&gt;User is redirected to the dashboard.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Robot, the same field under &lt;code&gt;params&lt;/code&gt; might be &lt;code&gt;robot: tests/auth/login.robot&lt;/code&gt;. Teams often add a custom key as well — &lt;code&gt;auto_tag: AUTH-001&lt;/code&gt; or &lt;code&gt;suite: 01_user_authentication&lt;/code&gt; — so filters and scripts stay boring. &lt;code&gt;params&lt;/code&gt; is a free key-value map; agree on a small schema in the repo README and stick to it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;automated: true&lt;/code&gt; is the coverage flag. &lt;code&gt;params&lt;/code&gt; is the pointer. Tags on the YAML side stay useful for smoke, priority, and feature filters — they do not have to duplicate the automation path.&lt;/p&gt;

&lt;p&gt;Treat agent output like any other PR: review &lt;code&gt;git diff&lt;/code&gt; before merge. A wrong tag match that flips &lt;code&gt;automated: true&lt;/code&gt; is worse than a missing link — catch it in review, or run the same rules in CI so chat is optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Git beats a TMS link field
&lt;/h2&gt;

&lt;p&gt;In a vendor database, linking is usually a URL paste or a fragile title search. Agents cannot see the catalog unless you build API glue.&lt;/p&gt;

&lt;p&gt;In Git, the agent (or a script) reads cases and specs in one workspace, and &lt;code&gt;git diff&lt;/code&gt; shows exactly which cases flipped to automated. Coverage is "scan tags ↔ case ids," not "update the spreadsheet after the sprint."&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete loop
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Feature lands on a branch.&lt;/li&gt;
&lt;li&gt;Agent drafts or updates manual YAML under &lt;code&gt;.gitoza-lite/test/cases/…/{CASE-ID}.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Automation engineer (or the same agent) implements Playwright / Robot and tags the test with &lt;code&gt;{CASE-ID}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Agent or script sets &lt;code&gt;automated: true&lt;/code&gt; and &lt;code&gt;params&lt;/code&gt; on the matching case.&lt;/li&gt;
&lt;li&gt;Manual testers execute what is still unautomated; Desktop dashboards can show automation rate when you use the full app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with one suite. Do not try to backfill five years of TestRail in a weekend. Ship the next feature with case + spec + link in the same PR.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it (free VS Code extension):&lt;/strong&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Gitoza on Marketplace&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Full post:&lt;/strong&gt; &lt;a href="https://gitoza.com/blog/ai-link-manual-automated-test-cases" rel="noopener noreferrer"&gt;gitoza.com/blog/ai-link-manual-automated-test-cases&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>robotframework</category>
    </item>
    <item>
      <title>Stop Using Raw WebDriver in Robot Framework</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Thu, 09 Jul 2026 18:14:55 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/stop-using-raw-webdriver-in-robot-framework-2jbd</link>
      <guid>https://dev.to/weiwen-weng/stop-using-raw-webdriver-in-robot-framework-2jbd</guid>
      <description>&lt;p&gt;A lot of Robot Framework projects still look like plain Selenium scripts with &lt;code&gt;.robot&lt;/code&gt; file extensions.&lt;/p&gt;

&lt;p&gt;Someone imports &lt;code&gt;webdriver&lt;/code&gt;, creates &lt;code&gt;driver = webdriver.Chrome()&lt;/code&gt;, then calls &lt;code&gt;find_element&lt;/code&gt; and &lt;code&gt;send_keys&lt;/code&gt; in Python helpers. Robot Framework runs the suite, but readable keywords, shared libraries, and consistent waits never show up in the tests.&lt;/p&gt;

&lt;p&gt;If you already use &lt;a href="https://robotframework.org/" rel="noopener noreferrer"&gt;Robot Framework&lt;/a&gt; with &lt;a href="https://robotframework.org/SeleniumLibrary/SeleniumLibrary.html" rel="noopener noreferrer"&gt;SeleniumLibrary&lt;/a&gt;, you do not need the raw WebDriver API. SeleniumLibrary gives you high-level keywords. The Page Object Model gives you structure. Together they keep tests short and UI changes localized.&lt;/p&gt;

&lt;p&gt;We published a small MIT template that shows the layout: &lt;a href="https://github.com/gitoza-io/rf-seleniumlibrary-pageobject-template" rel="noopener noreferrer"&gt;rf-seleniumlibrary-pageobject-template&lt;/a&gt;. It targets &lt;a href="https://www.saucedemo.com/" rel="noopener noreferrer"&gt;Sauce Demo&lt;/a&gt; — clone it, run four tests, fork the folder structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when you mix in raw WebDriver
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard_user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret_sauce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;login-button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fine for a script. Painful in a growing suite.&lt;/p&gt;

&lt;p&gt;Locators spread across helpers and test files. Waits become &lt;code&gt;time.sleep(2)&lt;/code&gt; in one place and missing in another. You end up maintaining SeleniumLibrary &lt;strong&gt;and&lt;/strong&gt; a parallel WebDriver stack. CI fails on a Tuesday night and you are not sure which path opened the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and after
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;driver.find_element(...).send_keys(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Login With Valid Credentials    ${VALID_USER}    ${VALID_PASSWORD}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locators in every file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LoginLocators.USERNAME&lt;/code&gt; in one module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ad-hoc sleeps&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wait_until_element_is_visible&lt;/code&gt; in &lt;code&gt;BasePage.click()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two browser stacks&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;SeleniumLibrary&lt;/code&gt; instance per suite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Four layers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Locators&lt;/td&gt;
&lt;td&gt;Selectors per screen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;login_locators.py&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BasePage&lt;/td&gt;
&lt;td&gt;Shared waits and actions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;click()&lt;/code&gt;, &lt;code&gt;enter_text()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page library&lt;/td&gt;
&lt;td&gt;Screen keywords&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LoginPage.login()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robot test&lt;/td&gt;
&lt;td&gt;Scenario only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Inventory Should Be Visible&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Folder layout in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources/locators/   → selectors
pages/                → Python page libraries
tests/                → thin .robot files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Locators stay in one place
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginLocators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;USERNAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id:user-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id:password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SUBMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id:login-button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer stable attributes like &lt;code&gt;data-test&lt;/code&gt; when the app exposes them.&lt;/p&gt;

&lt;h2&gt;
  
  
  BasePage owns the waits
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seleniumlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_until_element_is_visible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seleniumlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;seleniumlib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;BuiltIn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_library_instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SeleniumLibrary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import &lt;code&gt;SeleniumLibrary&lt;/code&gt; once in &lt;code&gt;common.resource&lt;/code&gt;. Page objects look up that same instance — no &lt;code&gt;driver&lt;/code&gt; argument passed between keywords. If you create &lt;code&gt;driver = webdriver.Chrome()&lt;/code&gt; outside SeleniumLibrary, your page keywords will not see that browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  A thin test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight robot_framework"&gt;&lt;code&gt;&lt;span class="gh"&gt;*** Test Cases ***&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;Valid Login&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nf"&gt;Login With Valid Credentials&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;${VALID_USER}&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;${VALID_PASSWORD}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nf"&gt;Inventory Should Be Visible&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No locators. No driver setup. Browser open/close in &lt;code&gt;Test Setup&lt;/code&gt; / &lt;code&gt;Test Teardown&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/gitoza-io/rf-seleniumlibrary-pageobject-template.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rf-seleniumlibrary-pageobject-template
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
robot &lt;span class="nt"&gt;--pythonpath&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--outputdir&lt;/span&gt; results tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;--pythonpath .&lt;/code&gt; from the repo root so &lt;code&gt;pages.*&lt;/code&gt; imports resolve. Four passing tests = layout is wired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating without a rewrite
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Move locators to &lt;code&gt;resources/locators/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;BasePage&lt;/code&gt; with &lt;code&gt;click&lt;/code&gt; / &lt;code&gt;enter_text&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wrap login as the first page library&lt;/li&gt;
&lt;li&gt;Import &lt;code&gt;SeleniumLibrary&lt;/code&gt; once in &lt;code&gt;common.resource&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rewrite one &lt;code&gt;.robot&lt;/code&gt; file, delete &lt;code&gt;webdriver&lt;/code&gt; imports from its helpers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repeat screen by screen. Leave the rest alone until you touch that feature.&lt;/p&gt;

&lt;p&gt;Playwright and Cypress are valid for greenfield work. Plenty of teams still run RF and SeleniumLibrary in production — for them, dropping &lt;code&gt;find_element&lt;/code&gt; for Page Objects is a low-risk upgrade that pays off on the next UI refactor.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Full guide:&lt;/strong&gt; &lt;a href="https://gitoza.com/blog/robot-framework-seleniumlibrary-page-object-model" rel="noopener noreferrer"&gt;gitoza.com/blog/robot-framework-seleniumlibrary-page-object-model&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Template repo:&lt;/strong&gt; &lt;a href="https://github.com/gitoza-io/rf-seleniumlibrary-pageobject-template" rel="noopener noreferrer"&gt;github.com/gitoza-io/rf-seleniumlibrary-pageobject-template&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>robotframework</category>
      <category>selenium</category>
      <category>python</category>
    </item>
    <item>
      <title>When Excel Stops Working for Test Case Management</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Sun, 05 Jul 2026 13:45:31 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/when-excel-stops-working-for-test-case-management-1i6k</link>
      <guid>https://dev.to/weiwen-weng/when-excel-stops-working-for-test-case-management-1i6k</guid>
      <description>&lt;p&gt;Most teams start with an Excel sheet. One tab per feature, columns for steps and expected results, maybe a status column someone colors green by hand. It is cheap, familiar, and fine for ten cases.&lt;/p&gt;

&lt;p&gt;Then the product grows. The sheet does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the sheet starts to hurt
&lt;/h2&gt;

&lt;p&gt;Excel stores text, not test runs. It does not know what "Pass" means in your release process, who executed a case yesterday, or which build you tested. Run history ends up in another tab, another file, or someone's memory.&lt;/p&gt;

&lt;p&gt;File names tell the rest of the story. &lt;code&gt;test-cases-v3-FINAL.xlsx&lt;/code&gt; becomes &lt;code&gt;v3-FINAL-John-edits.xlsx&lt;/code&gt; on a shared drive. Two people update the same row. Someone sorts a column and IDs no longer match the bug tracker. There is no diff, no reviewer, no clear answer to "who changed step 4 before we shipped."&lt;/p&gt;

&lt;p&gt;Meanwhile your app, automated tests, and CI live in Git. Manual cases sit in a file that never rides along with your repo history. When a feature changes, the sheet is usually one sync meeting behind.&lt;/p&gt;

&lt;p&gt;Spreadsheets are fine for a one-off checklist. They are a poor system of record once QA is ongoing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cases as files in the repo you already have
&lt;/h2&gt;

&lt;p&gt;The usual upgrade path is a cloud TMS. That solves collaboration, but your catalog moves into someone else's database — another login, another export, another place sensitive scenarios live.&lt;/p&gt;

&lt;p&gt;A lighter step for many teams is &lt;strong&gt;test cases as files in a repository you already control&lt;/strong&gt;. Each case is a small YAML file: metadata up top, steps and expected results in Markdown below. Git gives you history and review without a separate process. A thin editor on top lets you browse suites, edit cases, and record runs — pass/fail and notes saved as structured files, not colored cells.&lt;/p&gt;

&lt;p&gt;That is roughly how &lt;a href="https://gitoza.com" rel="noopener noreferrer"&gt;Gitoza&lt;/a&gt; works. You connect a repo (GitHub, GitLab, or any remote you already use), keep cases on a dedicated branch, and edit from VS Code or Cursor. Testers who do not live in the IDE can use the Desktop app for runs and dashboards. The VS Code extension is free for solo use and open-source projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changes
&lt;/h2&gt;

&lt;p&gt;Runs stop living in side tabs. You assign cases, record results, and the history stays next to the cases themselves — on the same branch, versioned with every sync.&lt;/p&gt;

&lt;p&gt;When a feature ships, you can see case changes in git commits — who changed what, and when — instead of chasing the latest spreadsheet on a shared drive. Tags, priorities, and links to automation specs are easier to keep consistent when each case is a file, not row 427 in a sheet that someone sorted last Tuesday.&lt;/p&gt;

&lt;p&gt;You do not need to rip out Excel tomorrow. If the sheet is fighting you on every release, moving cases into Git is often less painful than another "please use the latest file" email.&lt;/p&gt;

&lt;p&gt;More on the file format: &lt;a href="https://gitoza.com/blog/why-test-cases-belong-in-yaml" rel="noopener noreferrer"&gt;Why test cases belong in YAML&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it (free VS Code extension):&lt;/strong&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Gitoza on Marketplace&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team / manual testers:&lt;/strong&gt; &lt;a href="https://gitoza.com/#download" rel="noopener noreferrer"&gt;Gitoza Desktop&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full post:&lt;/strong&gt; &lt;a href="https://gitoza.com/blog/excel-test-case-management-alternative" rel="noopener noreferrer"&gt;gitoza.com/blog/excel-test-case-management-alternative&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>opensource</category>
      <category>git</category>
    </item>
    <item>
      <title>Test Management Compliance: Cloud SaaS vs Local-First</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Fri, 03 Jul 2026 16:03:51 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/test-management-compliance-cloud-saas-vs-local-first-57p8</link>
      <guid>https://dev.to/weiwen-weng/test-management-compliance-cloud-saas-vs-local-first-57p8</guid>
      <description>&lt;p&gt;Security sends a forty-question vendor questionnaire. Legal asks whether your test cases contain customer PII or staging screenshots. Procurement forwards next year's per-seat renewal.&lt;/p&gt;

&lt;p&gt;The decision is not which UI looks best in a demo. It is whether you can keep sensitive test knowledge off a vendor's cloud — without staffing a server farm.&lt;/p&gt;

&lt;p&gt;Test cases are not harmless notes. They hold user stories, credentials in steps, production-like screenshots, and run history. For finance, healthcare, or EU-facing teams, that is operational data — not "just QA."&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud SaaS vs local-first at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Cloud SaaS&lt;/th&gt;
&lt;th&gt;Local-first (e.g. Gitoza)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where is the canonical copy?&lt;/td&gt;
&lt;td&gt;Vendor cloud DB&lt;/td&gt;
&lt;td&gt;Your repo / your storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does test data cross vendor boundary?&lt;/td&gt;
&lt;td&gt;Yes, by design&lt;/td&gt;
&lt;td&gt;No vendor DB; sync via your Git remote or BYO S3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;Vendor activity log&lt;/td&gt;
&lt;td&gt;Git commit history (author, timestamp, diff)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical small-team cost model&lt;/td&gt;
&lt;td&gt;Per-seat subscription (hosted data)&lt;/td&gt;
&lt;td&gt;Lower per-seat subscription; no hosted TMS DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual tester UX&lt;/td&gt;
&lt;td&gt;Web UI&lt;/td&gt;
&lt;td&gt;Desktop UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit strategy&lt;/td&gt;
&lt;td&gt;Export / API&lt;/td&gt;
&lt;td&gt;Files already yours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Planning aid, not legal advice — your security team signs off.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where cloud TMS friction starts
&lt;/h2&gt;

&lt;p&gt;TestRail, Testmo, Qase, and similar tools store cases in a &lt;strong&gt;vendor-operated database&lt;/strong&gt;. Rollout is fast. Manual testers get web forms without a terminal.&lt;/p&gt;

&lt;p&gt;The compliance pitch is usually SOC 2, ISO 27001, and a DPA. That certifies &lt;strong&gt;the vendor's operation&lt;/strong&gt; — not whether &lt;strong&gt;your test catalog&lt;/strong&gt; belongs in their multi-tenant cloud.&lt;/p&gt;

&lt;p&gt;Question 12 on a typical security questionnaire: &lt;em&gt;Where is customer data processed?&lt;/em&gt; Your cases may include staging screenshots with real-looking records. Meanwhile your cases sit in a US region while your customers are in Frankfurt.&lt;/p&gt;

&lt;p&gt;On audit trails: activity logs help, but the system of record is still editable rows. Proving "we tested exactly this before release 4.2" often means trusting vendor retention — not a history your org already controls.&lt;/p&gt;

&lt;p&gt;Then there is seat math. Twenty testers at roughly $30–45 per seat per month adds up before renewals. Desktop tools like Gitoza also charge per seat — but you are not paying to host your test catalog in a vendor database.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about on-prem?
&lt;/h2&gt;

&lt;p&gt;Self-hosted TestRail Server or Jira + Xray behind the firewall can be right at enterprise scale. For a twenty-person QA org without a platform team, six figures all-in is not rare. Many regulated teams end up on cloud SaaS and hope the DPA holds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-first in one paragraph
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local-first&lt;/strong&gt; keeps the canonical copy in &lt;strong&gt;files you control&lt;/strong&gt; — typically a Git repo — while a &lt;strong&gt;desktop app&lt;/strong&gt; gives manual testers the UI they expect from SaaS.&lt;/p&gt;

&lt;p&gt;No vendor database as system of record. You pay a per-seat license for the app — not a recurring fee to store your test catalog in a vendor cloud.&lt;/p&gt;

&lt;p&gt;With Gitoza, cases live on a dedicated &lt;code&gt;gitoza&lt;/code&gt; branch in a shadow clone; testers do not touch &lt;code&gt;main&lt;/code&gt;. Git history is the audit log — every change is a commit with author, timestamp, and diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist before you shortlist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory&lt;/strong&gt; — What sensitive data is in cases and runs today?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Residency&lt;/strong&gt; — Where must it live? Can US-hosted SaaS meet your contracts?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canonical copy&lt;/strong&gt; — If you stop paying, what do you still own without an export project?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History integrity&lt;/strong&gt; — Can an admin alter past results without a durable trail?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seat math&lt;/strong&gt; — Three-year cost at current headcount and +50% growth?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual tester path&lt;/strong&gt; — Can non-engineers run tests without CLI or VPN?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor count&lt;/strong&gt; — How many new subprocessors does this tool add to annual review?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration&lt;/strong&gt; — Realistic effort to move in &lt;em&gt;and&lt;/em&gt; out in one release cycle?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If 2, 3, and 7 are red, cloud SaaS needs a harder look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you pick the dashboard
&lt;/h2&gt;

&lt;p&gt;A vendor's SOC 2 report describes how &lt;em&gt;they&lt;/em&gt; run their platform. Your auditor will still ask where &lt;em&gt;your&lt;/em&gt; case library lived when release 4.2 shipped — and whether anyone could rewrite that history without leaving a trace.&lt;/p&gt;

&lt;p&gt;Those are different questions. Certified cloud TMS vendors serve plenty of regulated customers well. Local-first does not replace legal review or a signed DPA. It just moves custody: the canonical copy sits in your repo, not a vendor row you export at renewal time.&lt;/p&gt;

&lt;p&gt;If custody matters in your industry — and for many QA teams it does — answer that on paper before the UI demo wins the room.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try Gitoza:&lt;/strong&gt; &lt;a href="https://gitoza.com/#download" rel="noopener noreferrer"&gt;Desktop download&lt;/a&gt; · &lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;VS Code extension&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why Manual Test Cases Should Live in YAML</title>
      <dc:creator>Weiwen Weng</dc:creator>
      <pubDate>Tue, 30 Jun 2026 18:34:39 +0000</pubDate>
      <link>https://dev.to/weiwen-weng/why-manual-test-cases-should-live-in-yaml-223k</link>
      <guid>https://dev.to/weiwen-weng/why-manual-test-cases-should-live-in-yaml-223k</guid>
      <description>&lt;p&gt;Most teams still treat manual test cases as rows in a SaaS database. That worked when cases were written slowly, reviewed rarely, and automation lived in a separate silo.&lt;/p&gt;

&lt;p&gt;It works less well now.&lt;/p&gt;

&lt;p&gt;AI can draft cases from screenshots and user stories in minutes. Automation lives next to application code. QA and dev share the same PRs. Auditors ask where test data lives and who changed what.&lt;/p&gt;

&lt;p&gt;In that world, test cases are data — and the format you choose matters as much as the tool UI.&lt;/p&gt;

&lt;p&gt;The durable direction is &lt;strong&gt;tests as code&lt;/strong&gt;: plain YAML files in version control, with a thin local layer for humans to browse, run, and review. Not because databases are evil, but because git + YAML matches how we already work with code, AI, and compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. AI is good at YAML — and YAML keeps your data yours
&lt;/h2&gt;

&lt;p&gt;LLMs are unusually good at structured text: YAML front matter plus a Markdown body is a sweet spot. Give the model a schema (&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;priority&lt;/code&gt;, steps, expected result) and a screenshot or user story, and you get a draft case in one pass.&lt;/p&gt;

&lt;p&gt;That matters for more than speed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boundary cases&lt;/strong&gt; — ask the model what you might have missed; it can reason about the scenario, not just paraphrase the story.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt; — the same format every time makes batch generation and review predictable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deeper point is &lt;strong&gt;data ownership&lt;/strong&gt;. Cases in a vendor DB are convenient until they are not: export limits, API friction, another system to secure, another place sensitive scenarios live. Local YAML in your repo is trivial for AI to read (including Cursor, Copilot, or whatever you use next), diff, and update — without shipping your test catalog to a third party. For many teams, that is a real security and efficiency win — not ideology.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Manual YAML beside automation makes coverage measurable
&lt;/h2&gt;

&lt;p&gt;When manual cases and automated tests sit in the same repository, a few things become boring in a good way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tag a case &lt;code&gt;automated: true&lt;/code&gt; and point &lt;code&gt;params&lt;/code&gt; at a Playwright or Selenium path — one file, one id.&lt;/li&gt;
&lt;li&gt;Automation coverage is a script or an AI pass away: scan specs, match case ids or titles, flip flags, report gaps.&lt;/li&gt;
&lt;li&gt;Automation rate stops being a quarterly spreadsheet exercise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example — a manual case linked to a Playwright spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login with valid credentials&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;smoke&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;playwright&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;playwright&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tests/auth/login.spec.ts&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## Steps&lt;/span&gt;
&lt;span class="s"&gt;1. Open the login page&lt;/span&gt;
&lt;span class="s"&gt;2. Enter valid credentials&lt;/span&gt;

&lt;span class="c1"&gt;## Expected result&lt;/span&gt;
&lt;span class="s"&gt;User is redirected to the dashboard.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The case filename is the case id. Tags and params give automation a handle without a separate traceability spreadsheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Git diffs are a review surface, not a workaround
&lt;/h2&gt;

&lt;p&gt;When AI generates cases at volume, &lt;strong&gt;git diff is the review UI&lt;/strong&gt;. You see exactly what changed: new case, edited steps, tag added, &lt;code&gt;automated&lt;/code&gt; flipped. PR review works the same as for application code.&lt;/p&gt;

&lt;p&gt;Test runs can be YAML too — one file per run, case paths and results inline — so run history is also versioned.&lt;/p&gt;

&lt;p&gt;Git's time machine means you can see what you tested three years ago, not what a database happened to retain after a migration.&lt;/p&gt;

&lt;p&gt;For QA leads, that is the difference between "we changed the suite" and "here is the diff."&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Local files, audit trails, and data sovereignty
&lt;/h2&gt;

&lt;p&gt;YAML on disk fits a lot of compliance expectations without extra product features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline / on-prem&lt;/strong&gt; — no requirement to sync cases or runs to a vendor cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper evidence&lt;/strong&gt; — commit hashes and (if you use them) GPG-signed commits attach authorship to changes; faking "we always had this test" is harder than editing a row with admin access in a hosted DB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data sovereignty&lt;/strong&gt; — the canonical copy lives in your repo, branch, and backup policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A database can be compliant. So can files. The difference is whether compliance is bolted on or native to how the artifact is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The UX problem — and why a local renderer matters
&lt;/h2&gt;

&lt;p&gt;The honest drawback: YAML is code. It is precise and diff-friendly; it is not fun to click through for a full-day test run.&lt;/p&gt;

&lt;p&gt;That gap is real. It is also why file-based test management tends to ship a &lt;strong&gt;local UI&lt;/strong&gt; that reads and writes the same YAML files on disk. The goal is not "no database anywhere" — it is &lt;strong&gt;no hosted database as the system of record&lt;/strong&gt;. A desktop client may keep a derived SQLite index or shadow clone under local app data for fast search and navigation; that cache is rebuildable from the files and never replaces what is committed in git. A VS Code extension can go further and touch only the workspace files — no separate index at all. Browse suites, edit cases, execute runs; YAML in the repo stays the source of truth.&lt;/p&gt;

&lt;p&gt;The workflow that tends to work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cases and test runs follow branches&lt;/strong&gt; — QA reviews case changes in the PR like any other change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Devs stay in the IDE&lt;/strong&gt; — open a test repository view, skim cases next to the fix they are working on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After a bugfix&lt;/strong&gt;, assign a case to a tester in the same branch and ship case + fix in one PR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;YAML holds the source of truth; the renderer is the lens. Tests as code with a usable surface — not a replacement for thinking about structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;YAML in git is not magic — you still need conventions, CODEOWNERS, and review discipline.&lt;/li&gt;
&lt;li&gt;AI-generated cases still need human review — diff-friendly format just makes that cheaper.&lt;/li&gt;
&lt;li&gt;This is not "dump TestRail tomorrow" for every org — migration cost and habit matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am claiming that if you want AI-assisted authoring, shared repos with automation, PR-based review, and strong auditability, file-based YAML cases age better than another hosted test database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see this workflow in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://gitoza.com" rel="noopener noreferrer"&gt;Gitoza&lt;/a&gt;&lt;/strong&gt; — the full platform: desktop client, git sync, review workflows, automation pipelines, and dashboards for the whole team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/gitoza-io/gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Gitoza Lite on GitHub&lt;/a&gt;&lt;/strong&gt; — open-source VS Code extension; cases under &lt;code&gt;.gitoza-lite/test/cases/&lt;/code&gt; and runs under &lt;code&gt;.gitoza-lite/test/run/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=gitoza.gitoza-yaml-test-cases" rel="noopener noreferrer"&gt;Install from the VS Code Marketplace&lt;/a&gt;&lt;/strong&gt; — open a repo, run &lt;strong&gt;Gitoza: Open Test Repository&lt;/strong&gt;, create a project, commit the YAML.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lite works directly on workspace YAML with no local index. Gitoza Desktop adds sync, dashboards, and a derived SQLite cache under &lt;code&gt;~/.gitoza/&lt;/code&gt; — still rebuilt from the same files on disk. Both treat YAML in git as the source of truth.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>yaml</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
