<?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: Jessica Sales Melo</title>
    <description>The latest articles on DEV Community by Jessica Sales Melo (@jessicasalestech).</description>
    <link>https://dev.to/jessicasalestech</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%2F4108153%2F6ff744a6-2c9a-4659-9801-ce3de6879a72.jpg</url>
      <title>DEV Community: Jessica Sales Melo</title>
      <link>https://dev.to/jessicasalestech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jessicasalestech"/>
    <language>en</language>
    <item>
      <title>Mutation Testing: Why Code Coverage Isn't Enough</title>
      <dc:creator>Jessica Sales Melo</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:48:06 +0000</pubDate>
      <link>https://dev.to/jessicasalestech/mutation-testing-why-code-coverage-isnt-enough-10p6</link>
      <guid>https://dev.to/jessicasalestech/mutation-testing-why-code-coverage-isnt-enough-10p6</guid>
      <description>&lt;h1&gt;
  
  
  Mutation Testing: Why Code Coverage Isn't Enough
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;How the Stryker tool reveals the true strength of your test suite — using a real business rule with clear limits.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;A green "100% code coverage" badge feels great. But here's the uncomfortable question QA should ask: &lt;strong&gt;coverage says a line ran — does it say the &lt;em&gt;behaviour&lt;/em&gt; was verified?&lt;/strong&gt; If someone changed that line, would your tests notice?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mutation testing&lt;/strong&gt; answers exactly that. In this article I show it with &lt;a href="https://stryker-mutator.io/" rel="noopener noreferrer"&gt;Stryker&lt;/a&gt; + Jest, applied to a grain-service-fee business rule (a nod to agribusiness domain rules).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full, runnable source on my GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is a mutant?
&lt;/h2&gt;

&lt;p&gt;Stryker makes a tiny change to your production code — a &lt;em&gt;mutant&lt;/em&gt; — and re-runs the tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tests fail&lt;/strong&gt; → mutant &lt;strong&gt;killed&lt;/strong&gt; ✅ (your test detected the change)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tests pass&lt;/strong&gt; → mutant &lt;strong&gt;survived&lt;/strong&gt; ❌ (that line is executed, but not truly verified)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation score = killed mutants / total mutants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The killer example: 100% coverage vs ~86% mutation score
&lt;/h2&gt;

&lt;p&gt;Two modules both report &lt;strong&gt;100% line, statement and branch coverage&lt;/strong&gt;. Only mutation testing tells them apart:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Coverage&lt;/th&gt;
&lt;th&gt;Mutation score (weak suite)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leap-year check&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100%&lt;/strong&gt; (strong)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taxa-service-fee rule&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;86% — 5 mutants survive&lt;/strong&gt; 🚨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fee rule is a classic off-by-one factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// isento (exempt)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;soja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.015&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The 5 surviving mutants are &lt;em&gt;business bugs waiting to ship&lt;/em&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surviving mutant&lt;/th&gt;
&lt;th&gt;Why it survives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;value &amp;lt;= 1000&lt;/code&gt; → &lt;code&gt;value &amp;lt; 1000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the &lt;strong&gt;exact value 1000&lt;/strong&gt; is never tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;value &amp;lt; 0&lt;/code&gt; → &lt;code&gt;value &amp;lt;= 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the value &lt;strong&gt;0&lt;/strong&gt; is never tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;typeof value !== 'number'&lt;/code&gt; → &lt;code&gt;false&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no test passes a &lt;strong&gt;non-numeric&lt;/strong&gt; value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;error &lt;strong&gt;message&lt;/strong&gt; changed to &lt;code&gt;''&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;tests check &lt;code&gt;toThrow(Type)&lt;/code&gt; but not the message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are off-by-one boundary bugs — precisely the defects that reach production wearing a green coverage badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: boundary + message tests
&lt;/h2&gt;

&lt;p&gt;Adding tests on the exact boundaries (0, 1000, non-numeric input) and asserting error messages kills every mutant → &lt;strong&gt;100% mutation score&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the quality gate
&lt;/h2&gt;

&lt;p&gt;Stryker can fail the build just like a failing test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"thresholds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"break"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;break: 95&lt;/code&gt;, a weak suite exits &lt;code&gt;1&lt;/code&gt;; a strong one exits &lt;code&gt;0&lt;/code&gt;. Wire this into CI and you turn &lt;em&gt;coverage&lt;/em&gt; into an &lt;em&gt;effectiveness gate&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost &amp;amp; levers
&lt;/h2&gt;

&lt;p&gt;Each mutant re-runs the suite — slow. Mitigations: &lt;code&gt;coverageAnalysis: "perTest"&lt;/code&gt; (only tests touching the line run), &lt;code&gt;--incremental&lt;/code&gt;, and mutate only changed files in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Equivalent mutants (the honest caveat)
&lt;/h2&gt;

&lt;p&gt;A mutation that can't change observable behaviour (e.g. a pure logging call) is &lt;em&gt;un-killable&lt;/em&gt; — that's why 100% isn't always the right goal. Use Stryker's thresholds and &lt;code&gt;// Stryker disable&lt;/code&gt; comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coverage ≠ quality.&lt;/strong&gt; Mutation testing measures your &lt;em&gt;assertions&lt;/em&gt;, not your execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surviving mutants are a to-do list&lt;/strong&gt;: missing test cases, weak assertions, or dead code to delete.&lt;/li&gt;
&lt;li&gt;Best where logic is dense in &lt;strong&gt;boundaries&lt;/strong&gt;: pricing, validation, permissions, business rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo (strong vs weak suite, the &lt;code&gt;SOLUTION=1&lt;/code&gt; toggle, and the CI quality gate) is on my GitHub — a great conversation starter for senior QA roles.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Jessica Sales — QA Engineer. When I say my tests are good, mutation testing is what lets me prove it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>codequality</category>
      <category>javascript</category>
      <category>jest</category>
    </item>
    <item>
      <title>AI-Powered Test Case Generation: Turning a Feature Description into Executable Gherkin</title>
      <dc:creator>Jessica Sales Melo</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:42:36 +0000</pubDate>
      <link>https://dev.to/jessicasalestech/ai-powered-test-case-generation-turning-a-feature-description-into-executable-gherkin-41a6</link>
      <guid>https://dev.to/jessicasalestech/ai-powered-test-case-generation-turning-a-feature-description-into-executable-gherkin-41a6</guid>
      <description>&lt;h1&gt;
  
  
  AI-Powered Test Case Generation: Turning a Feature Description into Executable Gherkin
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A practical look at plugging an LLM into the QA cycle — and why a controlled vocabulary keeps AI-generated tests runnable.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;As a QA Engineer, one of my biggest experiments this year was: &lt;strong&gt;can AI write tests that actually run?&lt;/strong&gt; The short answer is &lt;em&gt;yes — if you constrain the "creativity."&lt;/em&gt; In this article I share a working approach where you describe a feature in plain text and an &lt;strong&gt;AI generator produces Gherkin scenarios&lt;/strong&gt; executed by Playwright.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a write-up of a portfolio project. The full, runnable source is on my GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;feature description (.md)  →  🧠 AI generator  →  Gherkin (.feature)  →  Playwright runs it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. The goal: text → runnable Gherkin
&lt;/h2&gt;

&lt;p&gt;The user writes a natural-language spec (what the feature does and its acceptance criteria). The generator uses AI to produce a &lt;code&gt;.feature&lt;/code&gt; file. Then &lt;code&gt;playwright-bdd&lt;/code&gt; runs it for real.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The key design decision: a pluggable &lt;em&gt;IAProvider&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;The heart is a simple interface:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IAProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;generateFeature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;glossary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implementations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Used when&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deterministic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No API key&lt;/td&gt;
&lt;td&gt;Generates scenarios from curated templates, detected by keywords in the spec. Runs on &lt;strong&gt;any&lt;/strong&gt; environment (local and CI), no secret needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openai&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OPENAI_API_KEY&lt;/code&gt; set&lt;/td&gt;
&lt;td&gt;Uses a real LLM to derive &lt;strong&gt;new&lt;/strong&gt; scenarios from the description.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Selection is automatic: if a key exists, use the LLM; otherwise fall back to deterministic. This keeps the pipeline &lt;strong&gt;run-ready even with no credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The trick that makes AI tests &lt;em&gt;executable&lt;/em&gt;: a controlled glossary
&lt;/h2&gt;

&lt;p&gt;This is the most important lesson. If you let an LLM invent any step text, you end up with beautiful scenarios that &lt;strong&gt;cannot run&lt;/strong&gt; — because no step definition matches them.&lt;/p&gt;

&lt;p&gt;Solution: give the model a &lt;strong&gt;glossary of supported steps&lt;/strong&gt; and ask it to generate &lt;strong&gt;only&lt;/strong&gt; within that vocabulary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASSOS DISPONÍVEIS (use exactly these):
- Given I am on the page "PAGINA"
- When I check checkbox number N
- When I select the option "OPÇÃO" in the dropdown
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt also includes app context (credentials, expected messages). Result: AI-generated Gherkin always matches the step definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Prompt engineering for deterministic-enough output
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;temperature: 0.3&lt;/code&gt; → low creativity, high adherence.&lt;/li&gt;
&lt;li&gt;System prompt: "Generate ONLY the Gherkin file, starting with &lt;code&gt;# language: pt&lt;/code&gt;. Do not add commentary."&lt;/li&gt;
&lt;li&gt;Post-process: strip markdown fences the model sometimes wraps in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Validation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bddgen&lt;/code&gt; (missing-step detection) runs as a &lt;strong&gt;quality gate&lt;/strong&gt; — if the AI invents a step, generation fails early instead of at runtime. Then Playwright executes the scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI accelerates authoring&lt;/strong&gt;, but the &lt;em&gt;executable contract&lt;/em&gt; (glossary ↔ step definitions) is what keeps quality high.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;pluggable provider&lt;/strong&gt; makes the tool work everywhere: deterministic in CI, real LLM locally.&lt;/li&gt;
&lt;li&gt;This is a realistic template for "AI in QA" that isn't just a demo — it produces tests that run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source (with the deterministic provider, OpenAI provider, and the glossary) is public on my GitHub — a good starting point if you want to experiment with AI-assisted test generation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Jessica Sales — QA Engineer, exploring AI applied to the test automation lifecycle.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>playwright</category>
      <category>llm</category>
    </item>
    <item>
      <title>Playwright + BDD: Building an End-to-End Test Automation Framework from Scratch</title>
      <dc:creator>Jessica Sales Melo</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:42:35 +0000</pubDate>
      <link>https://dev.to/jessicasalestech/playwright-bdd-building-an-end-to-end-test-automation-framework-from-scratch-170p</link>
      <guid>https://dev.to/jessicasalestech/playwright-bdd-building-an-end-to-end-test-automation-framework-from-scratch-170p</guid>
      <description>&lt;h1&gt;
  
  
  Playwright + BDD: Building an End-to-End Test Automation Framework from Scratch
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A practical guide to building a maintainable E2E test suite with Playwright, TypeScript and Gherkin.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why this article?&lt;/strong&gt; As a QA Engineer, I often hear that test automation is "just clicking buttons with code." In this post I walk through a production-ready approach: a &lt;strong&gt;BDD-driven E2E framework&lt;/strong&gt; that is readable by devs/POs/QAs, easy to maintain, and green in CI — built with &lt;strong&gt;Playwright&lt;/strong&gt;, &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;playwright-bdd&lt;/strong&gt; (Gherkin in Portuguese, the Belgium of the project's origin, but the pattern applies to any language).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a write-up of a portfolio project — the full, runnable source is on GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Playwright&lt;/strong&gt; (1.6x)&lt;/td&gt;
&lt;td&gt;Reliable cross-browser automation (Chromium + Firefox here)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; (strict)&lt;/td&gt;
&lt;td&gt;Type safety across page objects and steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;playwright-bdd&lt;/strong&gt; (9.x)&lt;/td&gt;
&lt;td&gt;Writes scenarios in &lt;strong&gt;Gherkin&lt;/strong&gt; and executes them with the &lt;strong&gt;Playwright runner&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Page Objects (POM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Centralized selectors, no hardcoding in tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Allure Report&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rich report with steps, screenshots and history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CI that runs the suite and publishes the report on GitHub Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── features/            # BDD scenarios (Gherkin)
│   ├── login.feature
│   ├── inventory.feature
│   └── checkout.feature
├── steps/               # Step definitions (bridge Gherkin to code)
├── fixtures/            # Extended fixtures + injected Page Objects
├── src/pages/           # Page Objects
├── support/             # Environment config (.env)
└── playwright.config.ts # Playwright + playwright-bdd config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. BDD-first: write the feature, then the code
&lt;/h2&gt;

&lt;p&gt;The beauty of BDD is the &lt;strong&gt;conversation&lt;/strong&gt;. Scenarios are written in plain language, so Product Owners, Devs and QAs share the same understanding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kd"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Login
  &lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Login with valid credentials
    &lt;span class="nf"&gt;Given &lt;/span&gt;I am on the login page
    &lt;span class="nf"&gt;When &lt;/span&gt;I fill in the valid credentials
    &lt;span class="nf"&gt;Then &lt;/span&gt;I am taken to the products page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Bind Gherkin to code with step definitions
&lt;/h2&gt;

&lt;p&gt;Each &lt;code&gt;Given&lt;/code&gt;/&lt;code&gt;When&lt;/code&gt;/&lt;code&gt;Then&lt;/code&gt; maps to a step definition that uses &lt;strong&gt;fixtures&lt;/strong&gt; to inject Page Objects:&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="nc"&gt;Given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I am on the login page&lt;/span&gt;&lt;span class="dl"&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;loginPage&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I fill in the valid credentials&lt;/span&gt;&lt;span class="dl"&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;loginPage&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STANDARD_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;Then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I am taken to the products page&lt;/span&gt;&lt;span class="dl"&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;inventoryPage&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;inventoryPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Page Objects: keep selectors in one place
&lt;/h2&gt;

&lt;p&gt;A Page Object encapsulates the page's elements and actions, so the framework stays maintainable when the UI changes:&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;page&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#user-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#login-button&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="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&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="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submit&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;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Pluggable fixtures make steps concise
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;test.extend()&lt;/code&gt; + &lt;code&gt;createBdd()&lt;/code&gt;, the Page Objects are injected into every step — no globals, no setup duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Report &amp;amp; CI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allure&lt;/strong&gt; gives a rich, visual report; the CI publishes it on &lt;strong&gt;GitHub Pages&lt;/strong&gt; (a live, navigable link in your README).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robustness is a feature&lt;/strong&gt;: &lt;code&gt;workers: 1&lt;/code&gt; + navigation retries tame flaky network access to public demo apps — the same lesson applies to single-session systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BDD is a collaboration tool&lt;/strong&gt;, not just syntax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Objects + typed fixtures&lt;/strong&gt; = the difference between a script that breaks and a framework that scales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green CI with published reports&lt;/strong&gt; is what makes a portfolio credible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want the full code? Check out the open-source project linked on my GitHub, where all 7 scenarios run green in CI with an Allure report deployed to GitHub Pages.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Jessica Sales — QA Engineer, focused on end-to-end automation, API testing and applying AI to the QA lifecycle.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>playwright</category>
      <category>typescript</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
