<?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>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>
