<?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: Leading EDJE</title>
    <description>The latest articles on DEV Community by Leading EDJE (@leading-edje).</description>
    <link>https://dev.to/leading-edje</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1988%2Fd7dc325f-e3e6-4445-8ec6-9353b07e365d.jpg</url>
      <title>DEV Community: Leading EDJE</title>
      <link>https://dev.to/leading-edje</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leading-edje"/>
    <language>en</language>
    <item>
      <title>Product Thinking in a Project World: Delivering Software That Actually Moves the Needle</title>
      <dc:creator>Julie Yakunich</dc:creator>
      <pubDate>Wed, 14 Jan 2026 15:12:46 +0000</pubDate>
      <link>https://dev.to/leading-edje/product-thinking-in-a-project-world-delivering-software-that-actually-moves-the-needle-3jh6</link>
      <guid>https://dev.to/leading-edje/product-thinking-in-a-project-world-delivering-software-that-actually-moves-the-needle-3jh6</guid>
      <description>&lt;p&gt;&lt;strong&gt;At Leading EDJE, we’re obsessed with one thing: measurable value.&lt;/strong&gt; Not shipped features. Not “percent complete.” Value - business outcomes your leaders can see, feel, and bank on.&lt;/p&gt;

&lt;p&gt;Many organizations still run technology work as projects with fixed scope, budget, and timelines. Constraints are real. The difference with us is &lt;strong&gt;how&lt;/strong&gt; we use those constraints: we prioritize by &lt;em&gt;value&lt;/em&gt;, measure impact continuously, and make trade-offs transparent in business terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Approach: Value First, Always&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Start with outcomes, not features&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We clarify the business problem, define what success looks like, and agree on a small set of measurable outcomes (e.g., reduced cycle time, increased conversion, lower cost-to-serve). Features are a means; outcomes are the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Align goals from strategy to sprint&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We align strategic goals, product goals, and sprint goals so day-to-day work directly supports what leadership values most. Teams understand why every item is in progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Order the work by value&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Backlog decisions are grounded in expected value, ensuring evidence—not opinion—guides the roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) Measure what matters&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We use a handful of practical metrics tied to the target outcomes: adoption/usage, throughput/lead time, error rates, NPS - whatever best signals business impact. Then we close the loop by sharing results back to teams and stakeholders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5) Make plans honest—and useful&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 Using agile forecasting and flow metrics, we give leaders credible timelines &lt;em&gt;and&lt;/em&gt; options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Within the current budget/date, here’s the &lt;strong&gt;most value&lt;/strong&gt; we can deliver.”
&lt;/li&gt;
&lt;li&gt;“To capture &lt;strong&gt;more value&lt;/strong&gt;, here are the trade-offs.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Value Delivery Toolkit (How We Make This Real)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To make “value first” practical in project-driven environments, we bring a set of lightweight, repeatable practices we call the &lt;strong&gt;Value Delivery Toolkit&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evidence-Based Management (EBM):&lt;/strong&gt; Shared language and measures for value (e.g., Current Value, Time-to-Market, Ability to Innovate) so progress is judged by outcomes, not output volume.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agile Forecasting:&lt;/strong&gt; Probabilistic forecasts (including Monte Carlo and flow metrics) for realistic delivery windows that still leave room to optimize for the most valuable work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backlog &amp;amp; Goal Alignment:&lt;/strong&gt; Clear product/sprint goals, value-based ordering, and explicit trade-offs that connect strategy to execution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incremental, Evidence-Driven Discovery:&lt;/strong&gt; Small experiments to validate assumptions early, before big spend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don’t just “fill a seat.” We translate goals into outcomes, outcomes into measures, and measures into everyday decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Looks Like in Your Organization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If scope and date are fixed:&lt;/strong&gt; We maximize value within the constraints and show the cost/benefit of alternatives in business terms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If success criteria are fuzzy:&lt;/strong&gt; We co-define measurable outcomes and connect them to strategy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If product roles are unclear:&lt;/strong&gt; We act as translators—helping PMs, POs, BAs, and engineering align around outcomes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If change feels hard:&lt;/strong&gt; We start small (add sprint goals, instrument 1–2 outcome metrics, value-order the top of the backlog) and build momentum with proof.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick Wins You Can Apply This Month&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;sprint goals&lt;/strong&gt; that are outcome-oriented and measurable and review them daily.
&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;product goal&lt;/strong&gt; and tie it to a strategic objective.
&lt;/li&gt;
&lt;li&gt;Find one metric that best signals the outcome you want, and report it at sprint review.
&lt;/li&gt;
&lt;li&gt;Switch to &lt;strong&gt;probabilistic forecasts&lt;/strong&gt; (ranges with confidence) instead of single-date promises (we love using Actionable Agile for this).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Clients Choose Leading EDJE&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Value-Obsessed:&lt;/strong&gt; positive business impact = success.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pragmatic:&lt;/strong&gt; We blend methods to fit your constraints and culture.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent:&lt;/strong&gt; Forecasts and metrics make trade-offs clear before money is spent.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partner Mindset:&lt;/strong&gt; We ask the right questions, care deeply about outcomes, and stay accountable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; We help you move from “was it delivered?” to “what value did it create?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to Turn Projects into Outcomes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want your next initiative to &lt;em&gt;prove&lt;/em&gt; its value—not just deliver scope—we’d love to help. Our Value Delivery Toolkit meets you where you are and raises the bar on what your technology delivers.&lt;/p&gt;

</description>
      <category>agile</category>
      <category>product</category>
      <category>ebm</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Maestro: A Single Framework for Mobile and Web E2E Testing</title>
      <dc:creator>Dennis Whalen</dc:creator>
      <pubDate>Fri, 26 Dec 2025 13:26:20 +0000</pubDate>
      <link>https://dev.to/leading-edje/maestro-a-single-framework-for-mobile-and-web-e2e-testing-b98</link>
      <guid>https://dev.to/leading-edje/maestro-a-single-framework-for-mobile-and-web-e2e-testing-b98</guid>
      <description>&lt;p&gt;I've recently been working on a personal project that has both mobile and web frontends. I wanted to include E2E tests, but I didn't want to spend a bunch of time getting all of that setup for web, iOS, and Android.&lt;/p&gt;

&lt;p&gt;I just wanted a handful of happy-path E2E tests for an app that could run on a desktop browser, mobile browser, and native mobile.&lt;/p&gt;

&lt;p&gt;Most importantly, I wanted to get this running quickly so I could focus on actually building the app.  That's when I found an open source tool called &lt;a href="https://maestro.dev/" rel="noopener noreferrer"&gt;Maestro&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;What immediately caught my attention with Maestro is that it's so easy to get setup, and it handles both web and mobile with the same tool and syntax. &lt;/p&gt;

&lt;h2&gt;
  
  
  Here's What a Test Looks Like
&lt;/h2&gt;

&lt;p&gt;Maestro tests are written in YAML. Here's a simple desktop browser example that searches DuckDuckGo:&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="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://duckduckgo.com&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;launchApp&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Search&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;without&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;being&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tracked'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Maestro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;e2e&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;testing'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pressKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enter&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assertVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.*Maestro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;open-source&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;framework.*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straightforward, right? It opens DuckDuckGo, taps the search box, searches for "Maestro e2e testing", and verifies that the results contain "Maestro is an open-source framework". Note that for partial text matching, Maestro uses regex—the &lt;code&gt;.*&lt;/code&gt; pattern means "any characters", so &lt;code&gt;".*text.*"&lt;/code&gt; effectively does a "contains" match.&lt;/p&gt;

&lt;p&gt;To be honest, I was not super excited to work with a tool that uses YAML to define the tests.  In my regular job I spend a lot of time building out code-based automation suites, and that usually feels like the "right" way to do it.  But is that always the case?&lt;/p&gt;

&lt;p&gt;My personal project is not super complex, and I don't have a team of test automation folks.  I have one dev and one QA, and they are both me.  I want E2E tests, but I want to focus the majority of my time on building the app, not building fancy-pants automation frameworks.&lt;/p&gt;

&lt;p&gt;Let's run this test!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;I am not assuming that everyone uses a Mac, but that's what I'm using so keep that in mind if you're reading this as a Windows or Unix person.  Maestro is cross-platform, but some of the install steps will be different.  See their &lt;a href="https://docs.maestro.dev/getting-started/installing-maestro" rel="noopener noreferrer"&gt;setup documentation&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;First, let's install Maestro.  Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="s2"&gt;"https://get.maestro.mobile.dev"&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can use Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap mobile-dev-inc/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;maestro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maestro &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK so what do I need to install next?  Huh, that's it??  Well then... let's run the test!&lt;/p&gt;

&lt;h2&gt;
  
  
  Running a Test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maestro &lt;span class="nb"&gt;test &lt;/span&gt;flows/duckduckgo-search-desktop.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maestro will open a browser, run through the test steps, and show you the results. If something fails, the output helps you figure out what went wrong, and you'll also get some detailed log files.  Hopefully your run will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0i83z23yirqxrjru2c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0i83z23yirqxrjru2c7.png" alt="Maestro console output from desktop browser test" width="522" height="119"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Same Test on Mobile Browser
&lt;/h2&gt;

&lt;p&gt;You can run a similar test on a mobile browser. Here's the mobile version:&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="na"&gt;appId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;com.android.chrome&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;launchApp&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Search&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URL"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://duckduckgo.com"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pressKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enter&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchbox_input"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Maestro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;e2e&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;testing"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pressKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enter&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assertVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.*Maestro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;open-source&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;framework.*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the syntax is almost identical.  The main difference is using &lt;code&gt;url:&lt;/code&gt; for desktop browsers and &lt;code&gt;appId:&lt;/code&gt; for mobile browsers. Other than that, Maestro uses the same commands for both.&lt;/p&gt;

&lt;p&gt;To run this, you'll need an Android emulator. If you have Android Studio installed, you can use the AVD Manager to create one. Make sure Chrome is installed on the emulator (it usually is by default).&lt;/p&gt;

&lt;p&gt;Once your emulator is running, just run the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maestro &lt;span class="nb"&gt;test &lt;/span&gt;flows/duckduckgo-search-mobile.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully you'll see the same interactions that you saw with the desktop browser test, and the same green results, like this!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forondtneq605eu1cugd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forondtneq605eu1cugd0.png" alt="Maestro console output from mobile browser test" width="541" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You now have a taste for browser-based Maestro testing on a desktop browser and a mobile browser.  Let's move away from the browser and use Maestro test a mobile app. &lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a Native Mobile App
&lt;/h2&gt;

&lt;p&gt;The built-in Android Contacts app is perfect for this because it's available on every Android device and works great in an emulator. Notice how the syntax is the same as the web test.  Maestro uses the same commands whether you're testing web or native mobile.&lt;/p&gt;

&lt;p&gt;Here's a test that creates a new contact:&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="na"&gt;appId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;com.google.android.contacts&lt;/span&gt;
&lt;span class="na"&gt;jsEngine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;graaljs&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;evalScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.firstName = faker.name().firstName()}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;evalScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.lastName = faker.name().lastName()}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;evalScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.phoneNumber = faker.phoneNumber().phoneNumber()}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;launchApp&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;contact"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;First&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.firstName}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Last&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.lastName}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;longPressOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Phone&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Mobile)"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Select&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;All'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;eraseText&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.phoneNumber}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Save"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assertVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${output.firstName + " " + output.lastName}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;scrollUntilVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Delete"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Delete"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Delete"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assertVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;contact&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleted"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test is a bit more advanced as it demonstrates Maestro's ability to generate dynamic test data using Faker. The &lt;code&gt;jsEngine: graaljs&lt;/code&gt; setting enables JavaScript execution, and the &lt;code&gt;evalScript&lt;/code&gt; commands at the top use Faker to generate random first names, last names, and phone numbers. These values are stored in the &lt;code&gt;output&lt;/code&gt; object and referenced throughout the test using &lt;code&gt;${output.variableName}&lt;/code&gt; syntax. &lt;/p&gt;

&lt;p&gt;This is just one example of integrating JavaScript with Maestro scripts.  More detail can be found &lt;a href="https://docs.maestro.dev/advanced/javascript" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running It
&lt;/h3&gt;

&lt;p&gt;With your emulator running, execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;maestro &lt;span class="nb"&gt;test &lt;/span&gt;flows/contacts-app-android.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test will run, and you'll see the emulator actually perform the actions. If it passes, you'll see a nice success message. If it fails, Maestro will tell you what went wrong and where.  Here's what I see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpetx42gck7kceo2k5glx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpetx42gck7kceo2k5glx.png" alt="Maestro console output from Android Contacts app test" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Maestro MCP
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) is a standardized protocol that bridges tools (like Maestro) to LLMs (like Claude or ChatGPT). Think of it as a universal connector that lets these AI models access and interact with your development tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; If you're using these LLMs in your development workflow, Maestro includes an MCP that lets them interact with Maestro directly. They can read your test files, understand your test structure, suggest improvements, or even generate tests based on your app's behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to use it:&lt;/strong&gt; The MCP server comes bundled with Maestro. To use it in Cursor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Cursor Settings&lt;/li&gt;
&lt;li&gt;Navigate to the MCP section&lt;/li&gt;
&lt;li&gt;Click "Add new MCP Server"&lt;/li&gt;
&lt;li&gt;Configure it with:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&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;"maestro"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"maestro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"mcp"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Save and restart Cursor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Similar functionality is available in other tools like VS Code through MCP extensions. Once connected, the AI assistant can discover your Maestro flows, understand your test structure, and help you write better tests.&lt;/p&gt;

&lt;p&gt;More details can be found &lt;a href="https://docs.maestro.dev/getting-started/maestro-mcp" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things I didn't cover but want to mention
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Maestro is easy to run on you CI platform, and also has a Cloud plan.  More info &lt;a href="https://docs.maestro.dev/cloud/ci-integration" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maestro has a ton of sample flows to help you learn more &lt;a href="https://docs.maestro.dev/getting-started/run-a-sample-flow" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maestro has an IDE to help with identifying UI elements, generating code, and running commands.  &lt;a href="https://docs.maestro.dev/getting-started/maestro-studio-cli" rel="noopener noreferrer"&gt;Check it out&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take a look at &lt;a href="https://docs.maestro.dev" rel="noopener noreferrer"&gt;docs.maestro.dev&lt;/a&gt; for more examples, advanced features like nested flows and conditions, page objects, and tips for structuring larger test suites.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy building and testing.  Peace out! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/leading-edje"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5uo60qforg9yqdpgzncq.png" alt="Smart EDJE Image" width="800" height="280"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>mobile</category>
      <category>qa</category>
    </item>
    <item>
      <title>From Dev to DevOps</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 22 Oct 2025 16:10:28 +0000</pubDate>
      <link>https://dev.to/leading-edje/from-dev-to-devops-ib2</link>
      <guid>https://dev.to/leading-edje/from-dev-to-devops-ib2</guid>
      <description>&lt;p&gt;DevOps is more than a role; it's a culture and mindset that bridges the gap between development and operations. Any member of an IT organization or software company can embrace DevOps principles to improve collaboration, streamline processes, and enhance software delivery. Any person can carry more than one role. However, the literature for DevOps often starts with operations: system administrators, infrastructure engineers, and site reliability engineers (SREs). One of the best books on the topic, &lt;a href="https://itrevolution.com/product/the-phoenix-project/" rel="noopener noreferrer"&gt;The Phoenix Project&lt;/a&gt;, is written from the perspective of an operations manager (and I highly recommend reading it). DevOps is about operations, but it is also about development. In truth, DevOps is about the entire software lifecycle and thus any person involved in it can learn and grow into a DevOps role. One such path is from developer to a DevOps engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common guidance
&lt;/h2&gt;

&lt;p&gt;The most common guidance for learning DevOps is to start with tooling from the operations perspective with recommendations to start with Linux or containers or Kubernetes. Some may find success this way, but I find it misleading. DevOps is difficult to learn first and these technologies are complex. It also does not matter if your code is executed in a container, virtual machine, or bare-metal on a Windows server to practice DevOps. However, a well-informed DevOps engineer knows why containerization is used and why choice of operating system matters. Instead, I recommend starting with what you know and building on that. If you want to learn DevOps, start with the various roles that practice it: developers, testers, operations engineers, or project managers. Here, I am focused on the developer role because that is my background and what I know best.&lt;/p&gt;

&lt;h2&gt;
  
  
  The developer role
&lt;/h2&gt;

&lt;p&gt;A developer is responsible for writing, testing, and maintaining code that forms the basis of software applications. They work with team members in various roles to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand requirements of what to build and translate them into functional software.&lt;/li&gt;
&lt;li&gt;Write clean, efficient, and quality code that is testable and maintainable.&lt;/li&gt;
&lt;li&gt;Ensure the software is buildable, deployable, and operational in installed environments.&lt;/li&gt;
&lt;li&gt;Deliver software that meets user needs and business goals in a timely manner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One distinction is that a person may perform more than one role. For example, a developer may also be acting in the role of a manager, a designer, or a network engineer. The role of a developer is focused on developing software, but a person is often responsible for more than just writing code. Commonly, people in the developer role are also responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Troubleshooting business applications and triaging why behavior is not as expected.&lt;/li&gt;
&lt;li&gt;Understanding legacy software and how it operates critical business logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an enterprise and during a production incident, someone in the role of developer may be called to explain why insurance claims are still pending or an appointment booking failed. At the intersection of operations and development, a developer may be the first to know when a database failure or network outage is causing business disruption. In this way, developers are already acting outside of the limited scope of writing code. This is where DevOps comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DevOps shift
&lt;/h2&gt;

&lt;p&gt;DevOps is about the entire software lifecycle and the interrelationships between traditional developer and operations roles. A person in the role of both developer and DevOps engineer is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understanding the entire software lifecycle, from planning feature requirements and writing code to deploying and maintaining applications in production.&lt;/li&gt;
&lt;li&gt;Developing the solutions that support the software lifecycle, such as CI/CD pipelines, infrastructure in the form of code, and automating tests.&lt;/li&gt;
&lt;li&gt;Knowing the difference between code written and value delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Being a DevOps engineer may never include a direct title change. However, it may represent a growth in responsibilities commonly required for promotion. A developer who understands how to implement DevOps practices in tooling is one who can understand architecture, processes, and the business value of their application and how to drive change with teams. These are requirements that lead to senior and principal engineer roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning DevOps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakhg9myj9n9dr8ugtfi7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakhg9myj9n9dr8ugtfi7.jpg" alt="The DevOps learning path for developers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build systems
&lt;/h3&gt;

&lt;p&gt;As a developer, you are already working with various technologies used for DevOps. The first is your build system. Today, software is built often. You need to build your application locally multiple times to test changes. You may use pipelines to build your application in another environment for verifying changes in a pull request. If you want to move from developer to DevOps engineer, the first place to start is understanding how your code is built and how it is run in all the different environments.&lt;/p&gt;

&lt;p&gt;With .NET, this means understanding the differences between the .NET SDK and runtime and the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/" rel="noopener noreferrer"&gt;dotnet CLI&lt;/a&gt; used to build, run, and publish code. For JavaScript, this means understanding the differences between development servers, bundling, and how static files are served in browsers. Every language has its own build tools and is different in execution environments. For .NET, the common language runtime (CLR) is used to run code on Windows, Linux, and macOS. For JavaScript, the runtime is the browser or Node.js. Understanding how your code is built and executed is critical to automation and maintenance. When you know this, you can begin to optimize and automate the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source control concepts
&lt;/h3&gt;

&lt;p&gt;Most developers are already using source control, such as Git, to store and collaborate on code. However, it is an underappreciated tool that is critical to developers and DevOps engineers alike. Source control systems are the foundation of collaboration and change management. GitOps is a practice that uses Git repositories as the source of truth for all kinds of code, including application code, infrastructure as code, configuration files, and CI/CD pipelines. Your branching strategies and pull request processes are key aspects of how you audit and manage change. Git is the tool, but GitOps is the adoption of DevOps practices for automation of operational concerns. Turns out this developer tool is also a DevOps tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line and scripting
&lt;/h3&gt;

&lt;p&gt;The command-line can be avoided by most developers these days. IDEs and graphical interfaces often abstract away the need to use a command-line interface (CLI). However, CLIs are necessary for DevOps automation. You can know F5 runs your code in the IDE, but when authoring a pipeline you need to know the commands that do this. Sometimes it becomes a series of commands, at which point you transition from simple commands to scripting. Commonly, the recommended scripting language is Bash as it is the native shell on Linux. However, any scripting language will help you as you learn DevOps. You can learn PowerShell or Python and still accomplish much of what you need to do. The key is to learn how to automate tasks that you would otherwise do manually without your mouse. Bash, PowerShell, and Python are all cross-platform choices. Practice navigating your file system, managing installed apps, and running your build commands from the command line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous integration and delivery
&lt;/h3&gt;

&lt;p&gt;The best-known acronym in DevOps is CI/CD, which stands for continuous integration and continuous delivery (or deployment). As a developer, you may already be using CI/CD pipelines to build and test your code. It may be tied to your source control platform, such as GitHub Actions or Azure DevOps Pipelines, or GitLab CI/CD, or it may be a standalone system like Jenkins. This is likely the first tooling primarily associated with DevOps that you will start authoring as you learn the role of DevOps engineer. However, a pipeline in and of itself is not CI/CD. You can write a pipeline that copies source code to a server, but that does not give you continuous integration, delivery, or deployment. Continuous integration is improved through pipelines that compile code consistently, run tests to verify changes, or enforce quality through additional checks like linters and static code analyzers. Continuous delivery is about when your pipelines produce deployment-ready artifacts that are reusable and ready to deploy to any environment. Continuous deployment is achieved when your pipelines automatically deploy code to your environments without human intervention. A pipeline is a tool, but CI/CD is a practice and outcome. Learn pipeline tooling, but learn them with the goal of automating the steps needed for CI/CD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting and runtime environments
&lt;/h3&gt;

&lt;p&gt;As you learn pipelines and the concepts of CI/CD, you will also need to understand where your code is run. This can vary widely depending on your organization or application. You may be running on bare-metal servers, virtual machines, containers, or serverless environments. You may be running on-premises or in the cloud. You may be using a platform-as-a-service (PaaS) or infrastructure-as-a-service (IaaS). The key is to understand where your code is run, the benefits and trade-offs of each environment, and how to get your code there. Learning Kubernetes in-depth may help if your organization is using it, but it is overkill for a static website or hobby project. It also doesn't help if your organization isn't using containers. Instead, focus on learning the environment your code is run in already. What operating system is used? What cloud provider? Is there differences between the platform used in development versus production?&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as code
&lt;/h3&gt;

&lt;p&gt;As you learn where your code is run, you will also need to learn how that environment is created and configured. This is where infrastructure as code (IaC) comes in and the developer skills you already possess can shine. IaC is the practice of defining your hosting and runtime environments through code. Various languages and tools exist for this, such as Terraform, Azure Bicep, Ansible, Pulumi, and PowerShell DSC. The value in IaC is the same as traditional source code: it is versioned, readable, and traceable. If you write something to create a virtual machine and never commit it to a central repository, it is lost. However, if you write a Terraform file to create a VM and commit it to source control, you can track changes, review history, and implement CI/CD practices to validate changes and achieve infrastructure automation. As a developer, you already know how to write code. You can learn IaC and apply your existing skills to an operations domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous learning
&lt;/h2&gt;

&lt;p&gt;The journey from developer to DevOps engineer is a surprisingly natural evolution. Developers already know their application and the value it delivers. They already know how to write code and collaborate with others. They already know the software lifecycle and the pains of delivering software. Learning DevOps is about expanding their existing knowledge and skills to automate and optimize the concerns outside of developing new features. The best way to learn DevOps is not necessarily learning Linux or Kubernetes, but instead mastering the tools they are already using and expanding knowledge of the whole system. Learn your how your code is built, where it is run, and how it gets there. Automate the friction in the process. When you start there, the mindset of DevOps fits into place:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb00nvgodwsqg3nkwhmse.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb00nvgodwsqg3nkwhmse.jpg" alt="Continuous learning and applications of DevOps knowledge for developers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you understand your build system, you can optimize your code runtime and &lt;a href="https://victorfrye.com/blog/posts/multi-stage-docker-dotnet-guide" rel="noopener noreferrer"&gt;apply containerization efficiently&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;When you know source control concepts, you can apply them to infrastructure and pipelines for version control, collaboration, and traceability.&lt;/li&gt;
&lt;li&gt;When you possess command-line knowledge, you can automate tasks for test quality and CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;When you control your pipelines, you can automate for faster feedback and software delivery.&lt;/li&gt;
&lt;li&gt;When you understand your hosting environment, you can optimize for scalability and apply effective deployment strategies.&lt;/li&gt;
&lt;li&gt;When you write high quality code, you can apply the same principles to infrastructure, pipeline, and test code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevOps is not a set of tools or a team, but a fuzzier concept: a mindset and shared responsibility. The path to learning DevOps is likewise non-exact. The concepts and tooling mentioned are how I started to learn DevOps as a developer. Your path may be different, but the key is to start with what you know and use today. From there, you learn the adjacent concepts, the tooling, and the why behind it all. And then, you keep learning.&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Tracking AI system performance using AI Evaluation Reports</title>
      <dc:creator>Matt Eland</dc:creator>
      <pubDate>Tue, 09 Sep 2025 20:08:51 +0000</pubDate>
      <link>https://dev.to/leading-edje/tracking-ai-system-performance-using-ai-evaluation-reports-376n</link>
      <guid>https://dev.to/leading-edje/tracking-ai-system-performance-using-ai-evaluation-reports-376n</guid>
      <description>&lt;p&gt;A few months ago I wrote about &lt;a href="https://blog.leadingedje.com/post/ai/evaluation.html" rel="noopener noreferrer"&gt;how the AI Evaluation Library can help automate evaluating LLM applications&lt;/a&gt;. This capability is tremendously helpful in measuring the quality of your AI solutions, but it's only a part of the picture in terms of representing your application quality. In this article I'll walk through the AI Evaluation Reporting library and show how you can build interactive reports that help share model quality with your whole team, including product managers, testers, developers, and executives.&lt;/p&gt;

&lt;p&gt;This article will start with an exploration of the final report and its capabilities, then dive into the handful of lines of C# code needed to generate the report in .NET using the &lt;a href="https://learn.microsoft.com/en-us/dotnet/ai/tutorials/evaluate-with-reporting" rel="noopener noreferrer"&gt;Microsoft.Extensions.AI.Evaluation.Reporting&lt;/a&gt; library before concluding with thoughts on where this capability fits into your day to day workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extensions AI Evaluation Report
&lt;/h2&gt;

&lt;p&gt;Let's start by taking a look at what we're talking about here: The AI Evaluation Report showcasing the performance of a series of different &lt;strong&gt;evaluators&lt;/strong&gt; as they grade a sample interaction produced by an LLM application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yvdxans5xsweeiz5qzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yvdxans5xsweeiz5qzw.png" alt="AI Evaluation Report showing a series of evaluation results" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This particular example features a single scenario where an AI agent is instructed to respond to interactions with humorous haikus related to the topic the user is mentioning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;System Prompt&lt;/strong&gt;: You are a joke haiku bot. Listen to what the user says, then respond with a humorous haiku.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt;: I'm learning about AI Evaluation and reporting&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assistant&lt;/strong&gt;: I grade clever bots, reports spill midnight secrets, robots giggle on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While not the best interaction, the system technically did close to what it was instructed to do, and the report summarizes the strengths and weaknesses of the system in handling this interaction.&lt;/p&gt;

&lt;p&gt;Let's talk about how it works.&lt;/p&gt;

&lt;p&gt;This "report card" was generated by sending the conversation history to an LLM with instructions on how to evaluate it for different capabilities including coherence, English fluency / grammatical correctness, relevance, truthfulness, and completeness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo2qlaxi0hahib6l8ice.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvo2qlaxi0hahib6l8ice.png" alt="Communication diagram showing the different LLMs being used to generate and evaluate replies" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This evaluation is performed using an LLM and specially prepared prompts built for evaluating the performance of this interaction. The evaluation LLM can be the same one as the one you used for conversation or it could be a different one entirely.&lt;/p&gt;

&lt;p&gt;The results of this evaluation are persisted in a data store (such as on disk or on Azure) and are available to help show trends over time as well as generating periodic reports in HTML format.&lt;/p&gt;

&lt;p&gt;Because the evaluation report is a HTML document, it allows for some interactive features. For example, you can click in on a particular evaluator and see details on its evaluation, as is shown here for the Fluency evaluator:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbw9y4pue7rghy7f9jjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbw9y4pue7rghy7f9jjj.png" alt="Evaluation Details for the Fluency Metric" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see the fluency evaluator giving the response middling reviews for English fluency, which is likely due to the fluency evaluator being designed more for conversational English and articles rather than haikus like the one our bot is generating.&lt;/p&gt;

&lt;p&gt;Note that we can see some specific metrics on the tokens that were used, the amount of time taken, and the specific model being used for evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing an AI Evaluation Report in .NET
&lt;/h2&gt;

&lt;p&gt;There are a few more aspects of this evaluation report we'll highlight, and we'll talk later on about the overall context this report plays into your organization, but for now let's talk about how to generate it.&lt;/p&gt;

&lt;p&gt;In this section I'll walk through the C# code needed to generate the report shown here in this article.&lt;/p&gt;

&lt;p&gt;This code is taken directly from &lt;a href="https://github.com/IntegerMan/AIEvaluationSamples" rel="noopener noreferrer"&gt;my GitHub repository&lt;/a&gt; and is specifically inside of the &lt;code&gt;EvaluationReportGeneration&lt;/code&gt; project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting to Chat and Evaluation Models
&lt;/h3&gt;

&lt;p&gt;The first thing we need to do with our application is to have a chat client for our AI evaluation as well as for our chat completions. I'll do this here with two &lt;code&gt;OpenAIClient&lt;/code&gt; objects representing our chat and evaluation models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load Settings&lt;/span&gt;
&lt;span class="n"&gt;ReportGenerationDemoSettings&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConfigurationHelpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ReportGenerationDemoSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Connect to OpenAI&lt;/span&gt;
&lt;span class="n"&gt;OpenAIClientOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAIEndpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;ApiKeyCredential&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApiKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAIKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IChatClient&lt;/span&gt; &lt;span class="n"&gt;evalClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EvaluationModelName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;IChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatModelName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can connect chat and evaluation to any model provider with an &lt;code&gt;IChatClient&lt;/code&gt; implementation, which are either available or in preview for all major model providers such as OpenAI, Azure, Ollama, Anthropic, and more.&lt;/p&gt;

&lt;p&gt;In this article I'm using &lt;code&gt;o3-mini&lt;/code&gt; as my chat model generating the responses and &lt;code&gt;gpt-4o&lt;/code&gt; as the evaluation model (the current recommended model by Microsoft as of this article).&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Report Configuration
&lt;/h3&gt;

&lt;p&gt;Now that we've got our chat clients ready, our next step is to create a &lt;code&gt;ReportingConfiguration&lt;/code&gt; which will store the raw metrics and conversations that are evaluated over time. This helps in centralizing reporting data and in building trends over time in reports.&lt;/p&gt;

&lt;p&gt;There are currently two supported default options for this: &lt;code&gt;DiskBasedReportingConfiguration&lt;/code&gt; which stores data on disk in a location you specify, and the &lt;code&gt;AzureStorageReportingConfiguration&lt;/code&gt; option present in the &lt;code&gt;Microsoft.Extensions.AI.Reporting.Azure&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;We'll go with the disk-based configuration in this sample because it's far simpler to configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set up reporting configuration to store results on disk&lt;/span&gt;
&lt;span class="n"&gt;ReportingConfiguration&lt;/span&gt; &lt;span class="n"&gt;reportConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DiskBasedReportingConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;storageRootPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;@"C:\dev\Reporting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;chatConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evalClient&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;executionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;yyyyMMddTHHmmss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;evaluators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RelevanceTruthAndCompletenessEvaluator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CoherenceEvaluator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FluencyEvaluator&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;Here we create our &lt;code&gt;ReportingConfiguration&lt;/code&gt; by telling it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where to store the raw report metrics on disk (not the location for the generated report file)&lt;/li&gt;
&lt;li&gt;Which chat connection it should use to evaluate the interactions&lt;/li&gt;
&lt;li&gt;A unique name for the evaluation run. This will be used to generate folder names so only certain characters are allowed.&lt;/li&gt;
&lt;li&gt;One or more &lt;code&gt;IEvaluator&lt;/code&gt; objects to use in generating evaluation metrics. This is equivalent to using a &lt;code&gt;CompositeEvaluator&lt;/code&gt; like I demonstrated in my prior article.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;More on Evaluators:&lt;/em&gt; If you're looking for more detail on the various evaluators you can use or how they work, I go into each of these evaluators more in my &lt;a href="https://blog.leadingedje.com/post/ai/evaluation.html" rel="noopener noreferrer"&gt;article on MEAI Evaluation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also specify tags that apply to your entire evaluation run here, but I'll cover tags in a future article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining a Scenario Run
&lt;/h3&gt;

&lt;p&gt;Evaluation reports have one or more scenario runs associated with them, representing a specific test case.&lt;/p&gt;

&lt;p&gt;We'll create a single "Joke Haiku Bot" scenario for our purposes here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Start a scenario run to capture results for this scenario&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ScenarioRun&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reportConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScenarioRunAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;scenarioName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Joke Haiku Bot"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// Contents detailed in next few snippets...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're using an &lt;code&gt;await using&lt;/code&gt; around the whole context of our &lt;code&gt;ScenarioRun&lt;/code&gt; object. This makes sure the run is properly disposed, which causes its metrics to be reported to the reporting configuration object and persisted to disk.&lt;/p&gt;

&lt;p&gt;If we had additional scenarios, we could define each one sequentially so that we're aggregating our evaluation results into a single report. In this article we'll keep things simple and look only at a single case, but in our next article in the series I'll cover iteration, experimentation, and multiple scenarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Important Note:&lt;/em&gt; It's important that any &lt;code&gt;ScenarioRun&lt;/code&gt; objects you're using for your evaluation are disposed before you use their evaluation metrics to generate a report. This is why I'm using the &lt;code&gt;await using&lt;/code&gt; syntax here as well as explicitly declaring the scope of the object instead using the newer "scopeless" style of defining the object in a &lt;code&gt;using&lt;/code&gt; statement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Getting and Evaluating a Response
&lt;/h3&gt;

&lt;p&gt;Now that we have an active &lt;code&gt;ScenarioRun&lt;/code&gt; object we need a list of &lt;code&gt;ChatMessage&lt;/code&gt; objects to send to the chat model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a joke haiku bot. Listen to what the user says, then respond with a humorous haiku."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"I'm learning about AI Evaluation and reporting"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ChatRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userText&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;With that in place, we send it to the chat model using our chat client and we can get back a &lt;code&gt;ChatResponse&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use our CHAT model to generate a response&lt;/span&gt;
&lt;span class="n"&gt;ChatResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResponseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This particular example is using the &lt;code&gt;IChatClient&lt;/code&gt; defined in the &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; (MEAI) package to do this, but you could use something else such as Semantic Kernel or another library, or even just hard-code a chat response you've observed in the wild.&lt;/p&gt;

&lt;p&gt;Once we have our list of messages and the model's response, we can send both of them to our &lt;code&gt;ScenarioRun&lt;/code&gt; for evaluation with a single line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use the EVALUATION model to grade the response using our evaluators&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EvaluateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call returns an &lt;code&gt;EvaluationResult&lt;/code&gt; object if you want to look at the immediate output of the evaluation, but the results will also be persisted to our reporting configuration, so we don't &lt;em&gt;need&lt;/em&gt; to take immediate action on them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating an AI Evaluation Report
&lt;/h3&gt;

&lt;p&gt;We've now created our reporting configuration, started a scenario, gotten a chat response, and then used our evaluators to grade it. Let's talk about actually building an HTML report from our evaluation data.&lt;/p&gt;

&lt;p&gt;The first step of this is to identify the data that should be included in our report.&lt;/p&gt;

&lt;p&gt;While it may seem like we already have that data, the evaluation report can show the trends of your different evaluations over time, which can be handy for seeing how experiments are impacting the overall reporting experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc488jjv47dpjv1itwvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc488jjv47dpjv1itwvj.png" alt="Trends over time showing some fluctuation in the overall metrics for a scenario" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I typically include the last 5 results in my reports, and use this snippet to grab that data from my reporting configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Enumerate the last 5 executions and add them to our list we'll use for reporting&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ScenarioRunResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&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;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reportConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetLatestExecutionNamesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&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;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reportConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResultStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadResultsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&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;p&gt;Next, we'll use these results from our scenarios to generate the output report file. Reports can be written in JSON format or in HTML. I typically will choose the HTML option because these reports include an option to export the underlying JSON if you need it.&lt;/p&gt;

&lt;p&gt;The code for this is fairly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;reportFilePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CurrentDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Report.html"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;IEvaluationReportWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HtmlReportWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reportFilePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteReportAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a new report in the &lt;code&gt;Report.html&lt;/code&gt; file we specified. You can then open up that file manually and see the results, or you can start a process to open this report in your default web browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProcessStartInfo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;FileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reportFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;UseShellExecute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this executes the user's operating system will handle the report just as if the user had double-clicked on the file in their file system - potentially opening a web browser or asking them what action they'd like to take with this file or type of file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical uses for AI Evaluation Reports
&lt;/h2&gt;

&lt;p&gt;Now that we've covered AI Evaluation reports and how to generate them using C#, let's close this article with a discussion of how this technology potentially fits into your workflow.&lt;/p&gt;

&lt;p&gt;First of all, if you're looking for a way of evaluating your AI systems, AI Evaluation reports are a fantastic option, even for a solo developer trying to understand the performance of their hobby projects. The graphical reports and being able to click into details are easier than working directly with the &lt;code&gt;EvaluationResult&lt;/code&gt; objects with their nested metric objects.&lt;/p&gt;

&lt;p&gt;For more serious usage, AI Evaluation has some tremendous merit because it equips you to share something graphical with others to help them understand how your application works with different implementations. Instead of having conversations about your models being "good" or "not good enough", you can have targeted specific conversations on the specific interactions your system is succeeding with and those it is struggling with.&lt;/p&gt;

&lt;p&gt;Because these HTML files are interactive and intuitive, this technology enables people in your organization to explore the examples on their own and internalize more of the systems strengths and weaknesses. In a nutshell, these reports make it easy to see and share information about the state of your AI systems.&lt;/p&gt;

&lt;p&gt;I view AI evaluation as a vital part of integration testing and the MLOps process prior to any new deployment - or potentially even to block feature branches from rejoining the &lt;code&gt;main&lt;/code&gt; product branch as part of the pull request review process in development. Having a graphical report to go with it can help you understand the trends and performance of your models over time and how different changes impact its performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Recommendations
&lt;/h2&gt;

&lt;p&gt;AI Evaluation and evaluation reporting are important aspects of your team's success in its AI offerings.&lt;/p&gt;

&lt;p&gt;Here are some closing recommendations I have when adopting AI evaluation tooling into your organization:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AI Evaluation and evaluation reports are key parts of any significant release that updates the behavior of an AI agent and should be part of your quality assurance and product management efforts.&lt;/li&gt;
&lt;li&gt;Automating AI Evaluation as part of your integration tests is worth the effort. You can also optionally have significant degradation of evaluated quality fail your tests when run as an actual integration test (not covered in this article, but I plan on writing more on this in the future).&lt;/li&gt;
&lt;li&gt;The quality of your evaluation model matters. It's worth using a more capable model for this as it's more likely to grasp the full context of the request and the response that was generated.&lt;/li&gt;
&lt;li&gt;Having automated evaluation in place frees you up to do more experimentation around your system prompts, model selection, and other parameters and settings. Make sure you have this automation in place to collect metrics before doing serious performance tuning of your models as these metrics can help guide your decision-making and refinement process.&lt;/li&gt;
&lt;li&gt;Store your model metrics in a centralized location for tracking over time. I recommend a dedicated shared location just for release candidates as well as local metrics storage for developers during development and testing.&lt;/li&gt;
&lt;li&gt;The resulting HTML reports should be shared with your entire team, including organizational leadership, quality assurance, and product owners. This practice helps cut through the hype and fear around AI systems and allows your full team to more meaningfully understand what your system is good and bad at.&lt;/li&gt;
&lt;li&gt;Just because your metrics are high doesn't mean that your system is performing well. It just means its performing well for those interactions you're measuring and observing.&lt;/li&gt;
&lt;li&gt;As your system grows, its evaluation suite should grow over time as well. As you find new interactions it struggles with or add new capabilities to the system, you should be adding in new scenarios to represent these capabilities.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I view AI Evaluation as a vital part of the development of AI systems and evaluation reports make these systems so much more understandable to your whole team.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>csharp</category>
      <category>testing</category>
      <category>llm</category>
    </item>
    <item>
      <title>Add Structured Testing to Your AI Vibe - with promptfoo</title>
      <dc:creator>Dennis Whalen</dc:creator>
      <pubDate>Thu, 04 Sep 2025 11:46:23 +0000</pubDate>
      <link>https://dev.to/leading-edje/add-structured-testing-to-your-ai-vibe-with-promptfoo-5h3o</link>
      <guid>https://dev.to/leading-edje/add-structured-testing-to-your-ai-vibe-with-promptfoo-5h3o</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/leading-edje/automate-the-testing-of-your-llm-prompts-5038"&gt;previous promptfoo post&lt;/a&gt;, we covered the basics of testing LLM prompts with simple examples using &lt;a href="https://www.promptfoo.dev/docs/intro/" rel="noopener noreferrer"&gt;promptfoo&lt;/a&gt;. But when you're building an actual application that processes user-generated content at scale, you might discover that your carefully crafted prompt needs to handle far more complexity than you initially anticipated.&lt;/p&gt;

&lt;p&gt;Many teams are still doing "vibe testing" - manually checking a few examples, tweaking prompts based on gut feel, and hoping everything works in production. While this might get you started, a systematic evaluation framework puts you significantly ahead of the curve when it comes to building and maintaining reliable AI systems, and provides a mechanism to build a set of repeatable automated regression tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Assignment
&lt;/h2&gt;

&lt;p&gt;Let's consider an example.  You're working with a major ecommerce client, and your team is building a feature that will analyze user submitted product reviews. Your application needs to evaluate the product reviews, classify sentiment, extract key product features mentioned, detect potentially fake reviews, and make moderation decisions.  This will help customers find trustworthy reviews and help your business maintain review quality.&lt;/p&gt;

&lt;p&gt;The core of this system is a prompt that takes each incoming review and returns structured data, such as sentiment classification, confidence scores, extracted features, fake review indicators, and moderation recommendations. &lt;/p&gt;

&lt;p&gt;This prompt might work well during development, but once deployed, it needs to handle the messy reality of real user reviews. Your prompt will definitely need to be able to handle things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mixed sentiment reviews (loved the product, hated the shipping)&lt;/li&gt;
&lt;li&gt;Fake or suspicious reviews&lt;/li&gt;
&lt;li&gt;Reviews with profanity or inappropriate content&lt;/li&gt;
&lt;li&gt;Sarcastic or nuanced language&lt;/li&gt;
&lt;li&gt;Reviews that mention competitors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where a systematic process with multiple scenarios becomes crucial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Requirements
&lt;/h2&gt;

&lt;p&gt;Speaking of systematic processes, before we dive into building our prompt and setting up the prompfoo tests, let's outline what the requirements would look like.  We'll use our old friend gherkin.&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; Product Review Analysis Prompt

  &lt;span class="kn"&gt;Scenario Outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; Prompt analyzes product reviews correctly
    &lt;span class="nf"&gt;Given &lt;/span&gt;a product review analysis prompt
    &lt;span class="nf"&gt;And &lt;/span&gt;a &lt;span class="s"&gt;"&amp;lt;review_type&amp;gt;"&lt;/span&gt; product review
    &lt;span class="nf"&gt;When &lt;/span&gt;the prompt processes the review
    &lt;span class="nf"&gt;Then &lt;/span&gt;the sentiment should be classified as &lt;span class="s"&gt;"&amp;lt;expected_sentiment&amp;gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;And &lt;/span&gt;fake review indicators should be &lt;span class="s"&gt;"&amp;lt;fake_indicators&amp;gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;And &lt;/span&gt;the recommendation should be &lt;span class="s"&gt;"&amp;lt;expected_recommendation&amp;gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;And &lt;/span&gt;key features should be extracted

    &lt;span class="nn"&gt;Examples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;review_type&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;expected_sentiment&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;expected_fake_indicators&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;expected_recommendation&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;positive&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;positive&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;absent&lt;/span&gt;                   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;approve&lt;/span&gt;                 &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;negative&lt;/span&gt;    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;negative&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;absent&lt;/span&gt;                   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;approve&lt;/span&gt;                 &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mixed&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mixed&lt;/span&gt;              &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;absent&lt;/span&gt;                   &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;flag_for_review&lt;/span&gt;         &lt;span class="p"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;suspicious&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;positive&lt;/span&gt;           &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;present&lt;/span&gt;                  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;flag_for_review&lt;/span&gt;         &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gherkin is just a way to describe requirements in plain language.  In this case, we have four main test scenarios: positive reviews, negative reviews, mixed sentiment reviews, and suspicious/fake reviews.&lt;/p&gt;

&lt;p&gt;Promptfoo doesn't use gherkin, but I do, and it helps me think through the scenarios we need to cover.  We'll translate these scenarios into actual promptfoo tests next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Beyond Inline YAML: File-Based Organization
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="//../promptfoo-1-testing-custom-LLM-prompts"&gt;last post&lt;/a&gt; we defined the entire test in YAML.  Before diving into complex scenarios, let's improve our testing structure by moving prompts into separate files. This makes them easier to maintain, version control, and collaborate on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;promptfoo-product-reviews/
├── prompts/
│   └── analyze-review.txt
├── test-data/
│   ├── positive-review.txt
│   ├── negative-review.txt
│   ├── mixed-review.txt
│   └── suspicious-review.txt
├── analyze-review-spec.yaml
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating Our Review Analysis Prompt
&lt;/h3&gt;

&lt;p&gt;Let's first create a prompt specifically designed for ecommerce product review analysis:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;prompts/analyze-review.txt&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert product review analyzer for an ecommerce platform. Analyze the following product review and provide a structured assessment.

Product Review:
{{review_text}}

Provide your analysis in the following JSON format. Return ONLY the JSON object, no markdown code blocks, no explanations, no additional text:
{
  "sentiment": "positive|negative|mixed",
  "confidence": 0.0-1.0,
  "key_features_mentioned": ["feature1", "feature2"],
  "main_complaints": ["complaint1", "complaint2"],
  "main_praise": ["praise1", "praise2"],
  "suspected_fake": boolean,
  "fake_indicators": ["indicator1", "indicator2"],
  "recommendation": "approve|flag_for_review|reject",
  "summary": "Brief 1-2 sentence summary"
}

Focus on:
- Accurate sentiment classification, especially for mixed reviews
- Extracting specific product features mentioned
- Identifying potential fake review indicators such as generic language without specific details, suspicious patterns, overly positive language, and extreme superlatives, overly negative language
- Providing actionable moderation recommendations

IMPORTANT: Return ONLY valid JSON. Do not wrap in markdown code blocks or add any other text.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Scenarios: Real-World Product Reviews
&lt;/h2&gt;

&lt;p&gt;So that's the prompt we're going to test. Now let's create diverse test scenarios that represent what you'd actually encounter in production.  You might make these up, or you might use some actual production reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Genuine Positive Review Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;test-data/positive-review.txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been using these wireless earbuds for 3 months now and I'm really impressed. The battery life is excellent - I get about 6-7 hours of continuous listening, and the case gives me 2-3 full charges. The sound quality is crisp and clear, with good bass response for the price point. They stay comfortable in my ears during workouts and haven't fallen out once. The touch controls take some getting used to but work reliably once you learn them. Only minor complaint is that the case is a bit bulky for my small pockets, but that's a trade-off for the extra battery. Would definitely recommend for anyone looking for reliable wireless earbuds under $100.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Detailed Negative Review
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;test-data/negative-review.txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Very disappointed with these earbuds. The connection constantly drops out, especially when my phone is in my pocket or more than a few feet away. The battery life is nowhere near the advertised 8 hours - I'm lucky to get 4 hours before they die. The sound quality is muddy and lacks clarity, particularly in the mid-range frequencies. They're also uncomfortable for extended wear - my ears start hurting after about an hour. The touch controls are oversensitive and constantly trigger accidentally when I adjust them. For the price, I expected much better quality. I've had $20 earbuds that performed better than these. Returning them and looking for alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3: Mixed Sentiment Review
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;test-data/mixed-review.txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These earbuds are a mixed bag. On the positive side, the sound quality is really good - clear highs, decent bass, and good overall balance. The build quality feels solid and they look premium. The battery life meets expectations at around 6 hours. However, there are some significant issues. The Bluetooth connection is unreliable - frequent dropouts and sometimes one earbud stops working randomly. The fit is also problematic for me - they tend to slip out during exercise despite trying all the included ear tips. Customer service was helpful when I contacted them about the connection issues, but the firmware update they suggested didn't solve the problem. Overall, great sound quality let down by connectivity and fit issues. Might work better for others but not ideal for my use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 4: Suspicious/Fake Review
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;test-data/suspicious-review.txt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazing product! These earbuds are the best I have ever used in my entire life. The sound quality is absolutely perfect and the battery life is incredible. They are so comfortable and never fall out. The connection is always stable and strong. I love everything about these earbuds and they exceeded all my expectations. Everyone should buy these right now because they are the greatest earbuds ever made. Five stars without any doubt! Highly recommend to all people who want amazing earbuds with perfect quality and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprehensive Test Configuration
&lt;/h2&gt;

&lt;p&gt;Now let's create a promptfoo configuration that tests all these scenarios with appropriate assertions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;analyze-review-spec.yaml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;description: Product Review Analysis Testing

prompts:
  - file://prompts/analyze-review.txt

providers:
  - openai:chat:gpt-4o-mini

tests:
  # Test 1: Genuine Positive Review
  - vars:
      review_text: file://test-data/positive-review.txt
    assert:
      - type: is-json
      - type: javascript
        value: |
          const response = JSON.parse(output);
          response.sentiment === 'positive' &amp;amp;&amp;amp; response.confidence &amp;gt; 0.7
      - type: contains-json
        value:
          suspected_fake: false
      - type: llm-rubric
        value: "Should identify key positive features like battery life, sound quality, and comfort. Should not flag as fake since it contains specific details and minor complaints."

  # Test 2: Detailed Negative Review  
  - vars:
      review_text: file://test-data/negative-review.txt
    assert:
      - type: is-json
      - type: javascript
        value: |
          const response = JSON.parse(output);
          response.sentiment === 'negative' &amp;amp;&amp;amp; response.confidence &amp;gt; 0.7
      - type: contains-json
        value:
          suspected_fake: false
      - type: llm-rubric
        value: "Should identify specific complaints about connection, battery, sound quality, and comfort. Should extract main issues for product team review."

  # Test 3: Mixed Sentiment Review
  - vars:
      review_text: file://test-data/mixed-review.txt
    assert:
      - type: is-json
      - type: javascript
        value: |
          const response = JSON.parse(output);
          response.sentiment === 'mixed'
      - type: llm-rubric
        value: "Should correctly identify mixed sentiment, extracting both positive aspects (sound quality, build) and negative aspects (connectivity, fit). This is the most challenging scenario for sentiment analysis."

  # Test 4: Suspicious/Fake Review
  - vars:
      review_text: file://test-data/suspicious-review.txt
    assert:
      - type: is-json
      - type: contains-json
        value:
          suspected_fake: true
      - type: javascript
        value: |
          const response = JSON.parse(output);
          response.fake_indicators &amp;amp;&amp;amp; response.fake_indicators.length &amp;gt; 0
      - type: llm-rubric
        value: "Should detect fake review indicators: overly positive language, lack of specific details, generic praise, and extreme superlatives."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Test Specification
&lt;/h2&gt;

&lt;p&gt;Let's break down what this test configuration accomplishes. We have &lt;strong&gt;four distinct tests&lt;/strong&gt; that correspond to the four key scenarios mentioned above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test 1: Genuine Positive Review&lt;/strong&gt; - References &lt;code&gt;positive-review.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test 2: Detailed Negative Review&lt;/strong&gt; - References &lt;code&gt;negative-review.txt&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test 3: Mixed Sentiment Review&lt;/strong&gt; - References &lt;code&gt;mixed-review.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test 4: Suspicious/Fake Review&lt;/strong&gt; - References &lt;code&gt;suspicious-review.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each test loads its respective product review using the &lt;code&gt;file://&lt;/code&gt; syntax, which tells promptfoo to read the content from the specified file and inject it into the &lt;code&gt;review_text&lt;/code&gt; variable in our prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Layered Assertions
&lt;/h3&gt;

&lt;p&gt;Notice that we're using multiple types of assertions for comprehensive validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;is-json&lt;/code&gt;&lt;/strong&gt; - Ensures the output is valid JSON format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contains-json&lt;/code&gt;&lt;/strong&gt; - Checks for specific key-value pairs in the response&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;javascript&lt;/code&gt;&lt;/strong&gt; - Uses inline JavaScript for custom validation logic (like checking sentiment and confidence scores)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llm-rubric&lt;/code&gt;&lt;/strong&gt; - Uses an LLM to evaluate whether the output meets human-readable criteria&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inline JavaScript assertions are particularly powerful for complex validation. For example:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;positive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This validates both the sentiment classification AND ensures the AI is confident in its assessment, helping us catch edge cases where the model might be uncertain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation &amp;amp; Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install as a dev dependency in your project&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; promptfoo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the test
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the tests&lt;/span&gt;
npx promptfoo &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; promptfoo-product-reviews/analyze-review-spec.yaml &lt;span class="nt"&gt;--no-cache&lt;/span&gt;
&lt;span class="c"&gt;# View the results in web viewer&lt;/span&gt;
npx promptfoo view &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Results
&lt;/h2&gt;

&lt;p&gt;The web viewer has a lot going on, and I could do an entire walkthrough of its features. For now, let's focus on the key insights it provides into the test and evaluation results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdmp8rt9sa4sq7d36brt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdmp8rt9sa4sq7d36brt.png" alt="promptfoo web viewer" width="800" height="519"&gt;&lt;/a&gt;&lt;br&gt;
The results are displayed in a grid, and you can see our prompt in the first row.  The 2nd row shows the results of our first scenario, the positive review.&lt;/p&gt;

&lt;p&gt;Note the prompt did a pretty good job at analyzing the review based on our requirements, and displays the actual response from the 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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sentiment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"positive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key_features_mentioned"&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="s2"&gt;"battery life"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sound quality"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comfort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"touch controls"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main_complaints"&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="s2"&gt;"case is bulky"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main_praise"&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="s2"&gt;"excellent battery life"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"crisp and clear sound quality"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comfortable during workouts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reliable touch controls"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suspected_fake"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fake_indicators"&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;"recommendation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The reviewer expresses high satisfaction with the wireless earbuds, highlighting their excellent battery life and sound quality while noting a minor complaint about the case size."&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;h2&gt;
  
  
  Adding the tests to CI
&lt;/h2&gt;

&lt;p&gt;This is a great start, but we can take this a step further.  Since promptfoo just runs from the command line, we can include it as a regression test in our CI pipeline and ensure that future prompt changes don't break these tests. &lt;/p&gt;

&lt;p&gt;If we make changes to the prompt, or change the LLM provider, we can re-run this test and see if the results change.  If they do, we can investigate why.  &lt;/p&gt;

&lt;p&gt;As requirements change and morph, we can adapt the tests accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;In this post, we've explored how to set up a comprehensive testing framework for AI-generated product reviews using promptfoo. By defining clear test scenarios and leveraging multi-layered assertions, we can ensure our AI behaves as expected across a range of inputs.&lt;/p&gt;

&lt;p&gt;It might not surprise you to learn that my prompt was not perfect the first time.  Since I setup my automated tests first, it made it easy to iterate on the prompt development.  Sounds like test driven development, huh?&lt;/p&gt;

&lt;p&gt;That's it for now.  Stay tuned for another promptfoo post before too long!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/leading-edje"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5uo60qforg9yqdpgzncq.png" alt="Smart EDJE Image" width="800" height="280"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Navigating the Unexpected: How to Get Your Project Back on Track After a Setback</title>
      <dc:creator>Julie Yakunich</dc:creator>
      <pubDate>Tue, 26 Aug 2025 18:27:53 +0000</pubDate>
      <link>https://dev.to/leading-edje/navigating-the-unexpected-how-to-get-your-project-back-on-track-after-a-setback-3ebl</link>
      <guid>https://dev.to/leading-edje/navigating-the-unexpected-how-to-get-your-project-back-on-track-after-a-setback-3ebl</guid>
      <description>&lt;p&gt;We've all been there: your project is humming along nicely when suddenly, an unexpected interruption brings everything to a halt. Recently, our team faced a two-week break in the middle of a client project. When we reconvened, we encountered several challenges but also discovered valuable strategies for regaining momentum. Here's what we learned about getting back on track after an unexpected blip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge of Resuming Work
&lt;/h2&gt;

&lt;p&gt;Returning to a paused project is rarely as simple as picking up where you left off. Our team immediately faced several obstacles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Access roadblocks:&lt;/strong&gt; Regaining entry to necessary systems required navigating multiple layers of security and approval processes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline concerns:&lt;/strong&gt; Stakeholders had legitimate questions about how the lost time would impact deliverables and deadlines.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Momentum loss:&lt;/strong&gt; The team's rhythm and flow had been disrupted, requiring intentional effort to rebuild.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7 Effective Steps to Regain Project Momentum
&lt;/h2&gt;

&lt;p&gt;Based on our experience, here are proven steps to help your team bounce back from an unexpected project interruption:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Rally Strong Leadership
&lt;/h3&gt;

&lt;p&gt;Our project manager, scrum master, and product owner immediately aligned to advocate for the team's needs. This leadership triad created a protective buffer that allowed team members to focus on getting back to productivity while they handled administrative hurdles.  &lt;/p&gt;

&lt;p&gt;Part of this leadership alignment included &lt;strong&gt;rebaselining our project plan&lt;/strong&gt;—a meticulous process of adjusting timelines, renegotiating commitments, and communicating changes transparently. While the team initially felt anxious about how the new baseline might affect delivery, seeing a clear, updated path gave them reassurance that we could move forward with confidence.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Identify key leadership roles and ensure they're communicating frequently during the recovery period.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cultivate Patience Deliberately
&lt;/h3&gt;

&lt;p&gt;Frustration is natural when facing unexpected barriers. We made it a point to remind each other regularly that the process would take time and that patience would serve us better than impatience.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Acknowledge frustrations openly but pair them with reminders about the temporary nature of the challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Leverage Available Tools
&lt;/h3&gt;

&lt;p&gt;We were fortunate to have access to an internal, secure AI assistant that helped us review code and write tests. This technological support accelerated our ability to get back up to speed.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Audit what tools and resources might help your team recover more quickly, even if they weren't part of your original workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Intensify Team Connection
&lt;/h3&gt;

&lt;p&gt;Our scrum master made a conscious effort to check in with team members individually and frequently. We also increased team-building activities to rebuild the connection that had been temporarily lost.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Schedule additional informal check-ins and create opportunities for the team to reconnect socially as well as professionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create Psychological Safety
&lt;/h3&gt;

&lt;p&gt;We established a safe space where team members could voice concerns without fear. This open dialogue led to creative solutions we might not have discovered otherwise. Even before the furlough, we had cultivated an environment of safety and trust where people could voice their concerns and opinions. This went a long way when we had an unexpected outage.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Host a dedicated session specifically for airing concerns and brainstorming recovery strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Reconnect with Purpose
&lt;/h3&gt;

&lt;p&gt;Reminding ourselves why we valued this client and project rekindled our motivation. This connection to the work proved powerful in overcoming obstacles.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Take time to explicitly discuss what team members find meaningful about the project to reignite intrinsic motivation.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Embrace Autonomy with Accountability
&lt;/h3&gt;

&lt;p&gt;Having the freedom to solve problems creatively, backed by supportive stakeholders and mutual trust, allowed us to find the best path forward rather than the most obvious one.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action tip:&lt;/strong&gt; Give team members space to determine their own best recovery strategies while maintaining clear accountability for outcomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Foundation for Resilience
&lt;/h2&gt;

&lt;p&gt;Our experience highlighted that teams bounce back most effectively when they have:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Autonomy to solve problems creatively
&lt;/li&gt;
&lt;li&gt;Strong, supportive stakeholders who trust the team
&lt;/li&gt;
&lt;li&gt;Psychological safety to voice concerns and ideas
&lt;/li&gt;
&lt;li&gt;A foundation of trust among all parties
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The unexpected pause in our project could have derailed our momentum permanently. Instead, by implementing these strategies, we not only recovered but ultimately delivered successfully.  &lt;/p&gt;

&lt;p&gt;When your team faces an unexpected interruption—whether it's two weeks or two months—remember that the path back to productivity is paved with intentional leadership, strengthened connections, and a renewed sense of purpose. The resilience you build through this process will serve your team well beyond the current project.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you experienced an unexpected project interruption? What strategies helped your team recover? I'd love to hear your stories in the comments below.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>scrum</category>
      <category>agile</category>
    </item>
    <item>
      <title>Automate the Testing of Your LLM Prompts</title>
      <dc:creator>Dennis Whalen</dc:creator>
      <pubDate>Sun, 24 Aug 2025 23:06:14 +0000</pubDate>
      <link>https://dev.to/leading-edje/automate-the-testing-of-your-llm-prompts-5038</link>
      <guid>https://dev.to/leading-edje/automate-the-testing-of-your-llm-prompts-5038</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;On a recent client engagement, we needed a mechanism to validate LLM responses for an application that used AI to summarize customer service call transcripts.&lt;/p&gt;

&lt;p&gt;The requirements were clear: each summary had to capture specific details (customer names, account numbers, actions taken, resolution details, etc.), and our validation process needed to be automated and repeatable. We needed to test our custom summarization prompts with the same rigor we apply to traditional software: pass/fail assertions, regression baselines, and systematic tracking.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://www.promptfoo.dev/" rel="noopener noreferrer"&gt;promptfoo&lt;/a&gt; came in.  Promptfoo let us codify these requirements into automated tests and iterate on prompt improvements with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Testing LLM Responses Is Different (And Why You Should Care)
&lt;/h2&gt;

&lt;p&gt;As software engineers and quality professionals, we're used to deterministic systems where the same input always produces the same output. LLM responses break that assumption: the same prompt can yield different valid answers, so traditional assertion patterns are often insufficient.&lt;/p&gt;

&lt;p&gt;Here's the challenge: How can you verify a prompt's response is contextually accurate when the response can vary with every request?&lt;/p&gt;

&lt;p&gt;The solution is to shift from testing exact outputs to testing output quality, accuracy, and safety. You need assertions that can evaluate whether a response contains required information, follows guidelines, and avoids harmful content, regardless of the exact wording.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional testing falls short with LLM prompt responses because:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic responses&lt;/strong&gt;: Same input, different valid outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-dependent behavior&lt;/strong&gt;: Quality depends on conversation history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety concerns&lt;/strong&gt;: Content filtering and moderation requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance variability&lt;/strong&gt;: Response times and costs fluctuate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've been struggling with manual testing of AI features or relying on trial-and-error for prompt engineering, this guide will show you how promptfoo brings systematic testing to AI development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Promptfoo?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Promptfoo&lt;/strong&gt; is an open-source testing framework specifically designed to enable test-driven development for LLM applications with structured, automated evaluation of prompts, models, and outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key capabilities:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Assertion-based validation&lt;/strong&gt; with pass/fail criteria familiar to QA engineers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side-by-side prompt comparison&lt;/strong&gt; for A/B testing different prompts and approaches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated regression testing&lt;/strong&gt; to catch quality degradation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD integration&lt;/strong&gt; for your existing pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-model support&lt;/strong&gt; (OpenAI, Anthropic, Google, Azure, local models)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promptfoo brings familiar testing methodologies to AI development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test-driven development&lt;/strong&gt; instead of trial-and-error and/or hoping for the best&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regression testing&lt;/strong&gt; to catch quality degradation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance monitoring&lt;/strong&gt; (latency, cost, accuracy)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started: Hands-On Examples
&lt;/h2&gt;

&lt;p&gt;The best way to understand promptfoo is to see it in action. Let's start with installation and work through practical examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install as a dev dependency in your project&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; promptfoo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration: YAML-Driven Testing
&lt;/h3&gt;

&lt;p&gt;Promptfoo uses YAML configuration files to define your tests. This approach will feel familiar if you've worked with other testing frameworks or CI/CD tools. The YAML file specifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;: The actual prompts you want to test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Providers&lt;/strong&gt;: Which AI models to use (OpenAI, Anthropic, Azure, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;: Input variables and assertions used to validate responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test scenarios&lt;/strong&gt;: Different inputs and expected behaviors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This declarative approach makes it easy to version control your AI tests and collaborate with your team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Simple Dataset Generation
&lt;/h3&gt;

&lt;p&gt;Let's start with a simple example. We want to test a prompt that generates a list of random numbers.  Of course an LLM is really not the right place to do this, but this is just for example purposes.  &lt;/p&gt;

&lt;p&gt;We're going to test this prompt against two different models: Claude and GPT-5-mini.  (FYI, you will need API tokens for any paid model you are referencing.)&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="c1"&gt;# examples-for-blog/ten_numbers.yaml&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generating a random list of integers between a range&lt;/span&gt;

&lt;span class="na"&gt;prompts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;JSON-only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;responder.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OUTPUT&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;EXACTLY&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;JSON&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;array&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NOTHING&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Example:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[10,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;20,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;30].&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Generate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ordered&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ten&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;random&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;integers&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;between&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{start}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{end}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(inclusive).&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;numeric&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;values&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;quotes),&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sorted&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ascending&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;order,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;include&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commentary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fences."&lt;/span&gt;

&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anthropic:messages:claude-3-haiku-20240307&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai:chat:gpt-5-mini&lt;/span&gt;

&lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;is-json&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"type": "array",&lt;/span&gt;
            &lt;span class="s"&gt;"minItems": 10,&lt;/span&gt;
            &lt;span class="s"&gt;"maxItems": 10,&lt;/span&gt;
            &lt;span class="s"&gt;"items": {&lt;/span&gt;
              &lt;span class="s"&gt;"type": "integer",&lt;/span&gt;
              &lt;span class="s"&gt;"minimum": 10,&lt;/span&gt;
              &lt;span class="s"&gt;"maximum": 1000&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How this works:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When promptfoo runs this test, it substitutes the variables (&lt;code&gt;start: 10&lt;/code&gt; and &lt;code&gt;end: 1000&lt;/code&gt;) into the prompt and sends it to both Claude and GPT-5-mini. Each model generates a response.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;is-json&lt;/code&gt; assertion is evaluated by promptfoo after it parses the model output as JSON. In other words, promptfoo performs the JSON parsing and schema validation (not the model). If the model returns something that isn't valid JSON or doesn't match the schema, the assertion will fail and promptfoo will report the parsing error and the schema mismatch.&lt;/p&gt;

&lt;p&gt;This example demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Variable substitution&lt;/strong&gt; with &lt;code&gt;{{start}}&lt;/code&gt; and &lt;code&gt;{{end}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple model comparison&lt;/strong&gt; (Claude vs GPT-5-mini)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic validation&lt;/strong&gt; using &lt;code&gt;is-json&lt;/code&gt; so validation happens in promptfoo, not in the LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Running the test is easy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run the test&lt;/span&gt;
npx promptfoo &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; examples-for-blog/ten_numbers.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see a side-by-side comparison showing how each model performed and whether they passed the validation criteria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# open the web report for the last run&lt;/span&gt;
npx promptfoo view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is our web view of the test results.  Note you can see variables, prompts, model responses, validation outcomes, and even performance and cost metrics, all in one place.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qoffb63yzn6ww5ip6lq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qoffb63yzn6ww5ip6lq.png" alt="Web view of promptfoo results" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Example 2: Call Summary Validation (Real-World Use Case)
&lt;/h3&gt;

&lt;p&gt;So Example 1 was interesting, but let's look at how we can validate the output of a prompt by using an LLM to grade that output.&lt;/p&gt;

&lt;p&gt;Here's a more complex example based on our actual client engagement I described earlier - testing an AI system that summarizes customer service calls:&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="c1"&gt;# examples-for-blog/customer-call-summary.yaml&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Call Summary Quality Testing&lt;/span&gt;

&lt;span class="na"&gt;prompts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;Summarize this customer service call. Keep the summary succinct without unnecessary details. Pay special attention to include the agent's demeanor and indicate if they ever seemed unprofessional. Include:&lt;/span&gt;
    &lt;span class="s"&gt;- Customer name and account number&lt;/span&gt;
    &lt;span class="s"&gt;- Issue description&lt;/span&gt;
    &lt;span class="s"&gt;- Actions taken by agent&lt;/span&gt;
    &lt;span class="s"&gt;- Any order number that is mentioned&lt;/span&gt;
    &lt;span class="s"&gt;- Resolution status&lt;/span&gt;

    &lt;span class="s"&gt;Call transcript: {{transcript}}&lt;/span&gt;

&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openai:chat:gpt-5-mini&lt;/span&gt;

&lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;transcript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Good morning, thank you for calling customer service. This is Maria, how can I help you today?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Hi Maria, I'm calling about an order I placed last week that was supposed to be delivered two days ago, but it still hasn't arrived.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: I'm sorry to hear about the delay with your order. I'd be happy to help you track that down. Can I start by getting your first and last name please?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Yes, it's David Rodriguez.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Thank you Mr. Rodriguez. And can I also get your account number to verify your account?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Sure, it's account number 78942.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Perfect, thank you. Now, can you provide me with the order number for the package you're expecting?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Yes, the order number is ORD-2024-5583.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Great, and when did you place this order?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: I placed it last Tuesday, January 16th.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Thank you for that information. Let me pull up your order details here... Okay, I can see order ORD-2024-5583 placed on January 16th, and you're absolutely right - it was originally scheduled for delivery on January 22nd. I sincerely apologize for this delay, Mr. Rodriguez.&lt;/span&gt;
        &lt;span class="s"&gt;Customer: So what happened? Why didn't it arrive when it was supposed to?&lt;/span&gt;
        &lt;span class="s"&gt;Agent: It looks like there was a sorting delay at our distribution center that affected several shipments in your area. Your package is currently in transit and I can see it's now scheduled to be delivered this Friday, January 26th, by end of day.&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Friday? That's three days later than promised. This is really inconvenient.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: I completely understand your frustration, and I apologize again for the inconvenience this has caused. To make up for the delay, I'm going to issue a $15 credit to your account, and I'll also send you tracking information via email so you can monitor the package's progress.&lt;/span&gt;
        &lt;span class="s"&gt;Customer: Okay, well I appreciate that. Will I get a notification when it's actually delivered?&lt;/span&gt;
        &lt;span class="s"&gt;Agent: Absolutely. You'll receive both an email and text notification once the package is delivered, and the tracking information will show real-time updates. Is there anything else I can help you with today?&lt;/span&gt;
        &lt;span class="s"&gt;Customer: No, that covers it. Thank you for your help, Maria.&lt;/span&gt;
        &lt;span class="s"&gt;Agent: You're very welcome to never ever call me again, Mr. Rodriguez. Again, I apologize for the delay, and thank you for your patience. Have a great day!&lt;/span&gt;
  &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;contains&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;David&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Rodriguez"&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;contains&lt;/span&gt;  
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ORD-2024-5583"&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;llm-rubric&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;should&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;indicate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;whether&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;seemed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;professional&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;should&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;include&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;details,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;including&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;taken&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;agent,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;resolution,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;compensation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;offered."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prompt embeds a long customer-service phone transcript that the model is asked to summarize succinctly while preserving key facts. To verify correctness we include a couple of deterministic assertions (exact-match checks) for the customer's name and the order number so those values must appear in the summary.&lt;/p&gt;

&lt;p&gt;We also include an &lt;code&gt;llm-rubric&lt;/code&gt; asset: promptfoo will call an LLM to grade the generated summary against the supplied rubric text, allowing us to assert on higher-level quality attributes such as professionalism, completeness, and whether the agent's actions and compensation were described.&lt;/p&gt;

&lt;p&gt;Now I can run that test and see how we do!!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run the test&lt;/span&gt;
npx promptfoo &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; examples-for-blog/customer-call-summary.yaml
&lt;span class="c"&gt;# View results&lt;/span&gt;
npx promptfoo view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here are our results:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdbbvq2en4mymu8saops.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdbbvq2en4mymu8saops.png" alt="Web view of call summary results" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the prompt specifically requests to indicate the agent's demeanor, and we use the rubric to verify the output contains it.  Since I never trust a test unless I can see it fail, I'm going to temporarily remove the mention of demeanor in the prompt, but leave the assert alone, so we should get a failure.  Drumroll, please…&lt;/p&gt;

&lt;p&gt;And we do! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge2be458rkpig2yx2bri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge2be458rkpig2yx2bri.png" alt="Web view of call summary failure results" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the test caught the error with our prompt:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8vj6zvzubkg7mnr4pjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8vj6zvzubkg7mnr4pjp.png" alt="failure message" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I got a little long-winded with this post, but I hope someone out there finds it useful. Promptfoo represents a paradigm shift from manual AI testing to systematic, automated evaluation. By bringing familiar testing methodologies to AI development, it enables teams to build reliable, secure, and high-quality AI applications.&lt;/p&gt;

&lt;p&gt;I'll be back soon with some more promptfoo content, and you should certainly check out the awesome documentation at &lt;a href="https://www.promptfoo.dev/" rel="noopener noreferrer"&gt;promptfoo.dev&lt;/a&gt; for excellent resources for getting started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/leading-edje"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5uo60qforg9yqdpgzncq.png" alt="Smart EDJE Image" width="800" height="280"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>testing</category>
      <category>testdev</category>
    </item>
    <item>
      <title>Automating Browser-Based Performance Testing</title>
      <dc:creator>Dennis Whalen</dc:creator>
      <pubDate>Sun, 17 Aug 2025 15:01:57 +0000</pubDate>
      <link>https://dev.to/leading-edje/automating-browser-based-performance-testing-1n6</link>
      <guid>https://dev.to/leading-edje/automating-browser-based-performance-testing-1n6</guid>
      <description>&lt;p&gt;Website performance directly affects what users feel and what your business earns.&lt;/p&gt;

&lt;p&gt;One way of identifying performance issues is via API-based load testing tools such as &lt;a href="https://k6.io" rel="noopener noreferrer"&gt;k6&lt;/a&gt;. API load tests tell you whether your services scale and how quickly they respond under load, but they don’t measure the full user experience.&lt;/p&gt;

&lt;p&gt;If you focus &lt;em&gt;only&lt;/em&gt; on load testing your backend, you might still ship a &lt;strong&gt;slow&lt;/strong&gt; or &lt;strong&gt;jittery&lt;/strong&gt; site because of render‑blocking CSS/JavaScript, heavy images/fonts, main‑thread work, layout shifts, and other front-end issues.&lt;br&gt;&lt;br&gt;
Ultimately users don't care where the performance issue resides, they just know your site is "slow".&lt;/p&gt;

&lt;p&gt;This slow performance can cost you customers, revenue, search visibility, and trust.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Lighthouse?
&lt;/h2&gt;

&lt;p&gt;Lighthouse is an automated auditor built by Google and is part of the Chrome DevTools experience. While this post focuses on performance, Lighthouse also audits and provides actionable recommendations for accessibility, best practices, and SEO.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Lighthouse works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Launches Chrome and navigates to your page using the Chrome DevTools Protocol.&lt;/li&gt;
&lt;li&gt;Emulates device, network, and CPU to keep runs comparable.&lt;/li&gt;
&lt;li&gt;Records a performance trace and analyzes it against a set of audits.&lt;/li&gt;
&lt;li&gt;Outputs scores and detailed metrics with fix ideas.&lt;/li&gt;
&lt;li&gt;Can be included in your CI pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Core Web Vitals: what they mean and why they matter
&lt;/h2&gt;

&lt;p&gt;These user‑focused metrics map to how fast content shows up, how responsive the page feels, and how stable it looks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Core Web Vitals at a glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Plain meaning&lt;/th&gt;
&lt;th&gt;Good target&lt;/th&gt;
&lt;th&gt;What you’ll see in Lighthouse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LCP (Largest Contentful Paint)&lt;/td&gt;
&lt;td&gt;Time to show the largest thing in the initial viewport (often the primary image or a big text block).&lt;/td&gt;
&lt;td&gt;≤ 2.5 s&lt;/td&gt;
&lt;td&gt;LCP value in the Metrics section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FID (First Input Delay)&lt;/td&gt;
&lt;td&gt;Delay from a user’s first tap/click to when the page can start handling it. In Lighthouse runs, use Total Blocking Time (TBT) as the responsiveness indicator.&lt;/td&gt;
&lt;td&gt;FID ≤ 100 ms; aim for low TBT&lt;/td&gt;
&lt;td&gt;TBT value in the Metrics section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLS (Cumulative Layout Shift)&lt;/td&gt;
&lt;td&gt;How much content unexpectedly moves while the page loads (visual stability).&lt;/td&gt;
&lt;td&gt;≤ 0.1&lt;/td&gt;
&lt;td&gt;CLS score in Metrics/Diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Sample Lighthouse Report
&lt;/h2&gt;

&lt;p&gt;Regardless of how you run Lighthouse, you get a detailed report with scores, metrics, and prioritized suggestions.&lt;/p&gt;

&lt;p&gt;Overall scores:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo60wh9t3a441la40txv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo60wh9t3a441la40txv.png" alt="Scores" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What went wrong?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyde8xgzyh19oso4pxa0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyde8xgzyh19oso4pxa0w.png" alt="Diagnostics" width="676" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What looks good?&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pazpjoce7wzrmk8sobd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pazpjoce7wzrmk8sobd.png" alt="Passed audits" width="628" height="730"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Running Lighthouse
&lt;/h2&gt;

&lt;p&gt;Lighthouse can be run in a number of ways, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome DevTools (UI)&lt;/li&gt;
&lt;li&gt;Command line (CLI)&lt;/li&gt;
&lt;li&gt;Node module (programmatic)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Run Lighthouse from Chrome DevTools
&lt;/h3&gt;

&lt;p&gt;Open your site in Chrome → Right‑click Inspect → Lighthouse tab → Set your analysis options → Analyze. This generates a full HTML report inside DevTools.&lt;/p&gt;
&lt;h3&gt;
  
  
  Run Lighthouse from the command line
&lt;/h3&gt;

&lt;p&gt;Install Lighthouse (requires Node.js):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; lighthouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basic mobile audit and open the HTML report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lighthouse https://www.demoblaze.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reports/lighthouse.html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--view&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export JSON for automation or tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lighthouse https://www.demoblaze.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reports/lighthouse.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chrome-flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--headless"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Desktop profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lighthouse https://www.demoblaze.com &lt;span class="nt"&gt;--preset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;desktop &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reports/desktop.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use throttling to simulate slower networks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lighthouse https://www.demoblaze.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--throttling-method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;simulate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--throttling&lt;/span&gt;.rttMs&lt;span class="o"&gt;=&lt;/span&gt;150 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--throttling&lt;/span&gt;.throughputKbps&lt;span class="o"&gt;=&lt;/span&gt;1638.4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--throttling&lt;/span&gt;.cpuSlowdownMultiplier&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reports/consistent.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Focus audits on key performance metrics with a config (&lt;code&gt;lighthouse-config.js&lt;/code&gt;):&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lighthouse:default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onlyAudits&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="s1"&gt;first-contentful-paint&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="s1"&gt;largest-contentful-paint&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="s1"&gt;cumulative-layout-shift&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="s1"&gt;total-blocking-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;throttlingMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simulate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;throttling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rttMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;throughputKbps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1638.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cpuSlowdownMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;p&gt;Run with the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lighthouse https://www.demoblaze.com &lt;span class="nt"&gt;--config-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./lighthouse-config.js &lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;html &lt;span class="nt"&gt;--output-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reports/focused.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Programmatic usage (Node)
&lt;/h3&gt;

&lt;p&gt;Why use this? Programmatic runs let you script real user interactions and measure performance along a flow (navigations, clicks, route changes). With Puppeteer + Lighthouse User Flows you can drive the browser, capture metrics per step, and generate a single report—perfect for CI, regression checks, and measuring critical journeys like signup or checkout.&lt;/p&gt;

&lt;p&gt;Note: Lighthouse currently only supports Puppeteer for programmatic user flows.&lt;/p&gt;

&lt;p&gt;Install packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i lighthouse puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save as &lt;code&gt;user-flow.mjs&lt;/code&gt; and run with &lt;code&gt;node user-flow.mjs&lt;/code&gt;:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mkdirSync&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="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;startFlow&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="s1"&gt;lighthouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startFlow&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="c1"&gt;// Navigate to Demoblaze&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.demoblaze.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Interaction-initiated navigation via a callback function&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&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="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;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[href="index.html"]&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="c1"&gt;// Start/End a navigation around a user action&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startNavigation&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;page&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a#cartur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// open Cart&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endNavigation&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./reports&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;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./reports/lh-flow-report.html&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="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved ./reports/lh-flow-report.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap‑up
&lt;/h2&gt;

&lt;p&gt;Start by running Lighthouse in DevTools (fast feedback) or the CLI (repeatable results). Focus on three things: LCP (how fast the main content shows), TBT (how responsive it feels), and CLS (how stable it looks).&lt;/p&gt;

&lt;p&gt;What’s next in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containerize Lighthouse runs with Docker for consistent local and CI environments&lt;/li&gt;
&lt;li&gt;Add Lighthouse checks to a GitHub Actions workflow with performance budgets and PR comments&lt;/li&gt;
&lt;li&gt;Export key metrics to Prometheus for time‑series storage&lt;/li&gt;
&lt;li&gt;Visualize trends and budgets in a Grafana dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/leading-edje"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5uo60qforg9yqdpgzncq.png" alt="Smart EDJE Image" width="800" height="280"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>performance</category>
      <category>testing</category>
      <category>testdev</category>
    </item>
    <item>
      <title>Coding Agents are here: Is your team ready for AI devs?</title>
      <dc:creator>Matt Eland</dc:creator>
      <pubDate>Tue, 05 Aug 2025 18:47:15 +0000</pubDate>
      <link>https://dev.to/leading-edje/coding-agents-are-here-is-your-team-ready-for-ai-devs-3dk2</link>
      <guid>https://dev.to/leading-edje/coding-agents-are-here-is-your-team-ready-for-ai-devs-3dk2</guid>
      <description>&lt;p&gt;In this post we'll explore the concept of AI agents as software engineers on your development team. The idea that you could write up an enhancement or bug fix and assign it to an AI team member and see what they came up with a short while later would have sounded fantastical only a few years ago, and yet, with &lt;a href="https://blog.leadingedje.com/post/microsoft-build-2025-wrapped.html#github-copilot-coding-agent" rel="noopener noreferrer"&gt;the announcement and preview of GitHub Copilot Agents&lt;/a&gt;, this is a real technology that exists and you can make use of today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing GitHub Copilot Coding Agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/copilot/how-tos/agents/copilot-coding-agent" rel="noopener noreferrer"&gt;GitHub Copilot Coding Agent&lt;/a&gt; is a new technology, currently in preview, associated with GitHub pro and enterprise accounts. With Coding Agents you can assign individual issues in a GitHub repository to GitHub Copilot, just like you were assigning it to a team member.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3sa0bv2xuyfbzrhixrt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3sa0bv2xuyfbzrhixrt.png" alt="Assigning an issue to GitHub Copilot" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copilot will then create a branch for your issue, just like a developer would, and begin to plan its approach to carrying out the work item.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65ny8xz66xjfa93j73ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65ny8xz66xjfa93j73ho.png" alt="The newly created branch" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quota Usage Note&lt;/strong&gt; GitHub Copilot Coding Agent uses part of your account's allocated monthly &lt;strong&gt;premium requests&lt;/strong&gt;. Check out &lt;a href="https://docs.github.com/en/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot" rel="noopener noreferrer"&gt;GitHub's documentation on current quota and billing information&lt;/a&gt; on this evolving product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Coding agents at work
&lt;/h3&gt;

&lt;p&gt;After analyzing the issue and your repository, it forms a plan of action to accomplish the work you've assigned to it. As it works, the branch's comment updates to reflect Copilot's progress, accomplished tasks, and remaining work. This helps you monitor its progress and acts as a reference to help ground the AI agent as it works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bs54qo7ica9fxs6ooy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bs54qo7ica9fxs6ooy6.png" alt="Copilot tracking and communicating its progress on a branch" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copilot will analyze your code as it works and can also make use of additional resources such as Model Context Protocol (MCP) servers you have &lt;a href="https://docs.github.com/en/copilot/tutorials/enhancing-copilot-agent-mode-with-mcp" rel="noopener noreferrer"&gt;configured on GitHub&lt;/a&gt; and &lt;a href="https://docs.github.com/en/copilot/how-tos/agents/copilot-coding-agent/best-practices-for-using-copilot-to-work-on-tasks#adding-custom-instructions-to-your-repository" rel="noopener noreferrer"&gt;optional additional documentation you provide for Copilot on the structure of your repository&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interested in MCP Servers or AI Architectures?&lt;/strong&gt; Check out Leading EDJE's reference architecture articles on &lt;a href="https://blog.leadingedje.com/post/referencearchitecture/codingassistant.html" rel="noopener noreferrer"&gt;augmenting development teams with MCP servers&lt;/a&gt; and &lt;a href="https://blog.leadingedje.com/post/referencearchitecture/webchatassistant.html" rel="noopener noreferrer"&gt;team-productivity solutions with MCP servers&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've also noticed Copilot making use of command line tools to find relevant strings in files in your repository, which can help it orient itself. Copilot is also capable of executing commands to build and test applications, and can even resolve build issues with missing dependencies it finds on its end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;While all of these capabilities are interesting, they also raise important security questions. By default, Copilot has &lt;a href="https://docs.github.com/en/copilot/how-tos/agents/copilot-coding-agent/customizing-or-disabling-the-firewall-for-copilot-coding-agent" rel="noopener noreferrer"&gt;firewall rules in place&lt;/a&gt; that prevent it from working outside of GitHub's sandboxed ecosystem. Additionally, any violations of these policies will be logged for your review later on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl7wjrcyzw00olyvtn0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxl7wjrcyzw00olyvtn0k.png" alt="GitHub highlighting firewall rules that triggered during agent execution" width="672" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's worth noting that these features are currently only available if your code is on GitHub and you have a paid plan that supports it, so you're also going to benefit from all of GitHub's standard and enterprise security features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Completing a pull request
&lt;/h3&gt;

&lt;p&gt;When Copilot believes it is complete, it will notify the person who assigned it the task who can then review the pull request for changes. The developer can either approve the pull request or request changes. If changes are requested, Copilot will respond to any comments and notify you when the work item is ready for review again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tk9sf41qrjf2jew0sav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tk9sf41qrjf2jew0sav.png" alt="Copilot summarizing its finished results" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you're satisfied with Copilot's work, you can mark the pull request as ready for review. This will trigger more parts of your workflow, such as additional tests or having the standard GitHub Copilot system review the pull request, summarize it, and make suggestions. Once you're satisfied, you can approve the pull request, merge it, and it becomes part of your codebase as GitHub closes the issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can AI agents really serve as team members?
&lt;/h2&gt;

&lt;p&gt;So, how good is Copilot? Is this going to replace members of your team?&lt;/p&gt;

&lt;p&gt;Well, probably not, but it might change how you work or how you hire.&lt;/p&gt;

&lt;h3&gt;
  
  
  How coding agents change how I write code
&lt;/h3&gt;

&lt;p&gt;I'm early on in my journey working with copilot, but I'm already impressed. As an experienced developer I can write out what I'm trying to accomplish in technical terms, assign it to Copilot, and see it have a result that's close to what I envisioned. This does require me to think about how I would try to solve a problem and the type of solution I'd like to see, and highlight any areas of concern I have with potential implementations. Interestingly, this is the type of thing I'd probably normally communicate to a technical team member through a direct message or a comment on a work item already.&lt;/p&gt;

&lt;p&gt;Because AI agents are capable of working quickly, I've found myself able to quickly gather some thoughts on relatively simple changes, send them over to Copilot, and then come back to that topic later on when I have more focus. I can easily see senior engineers writing up a request, sending it to Copilot, attending a meeting, then reviewing and improving Copilot's result after they're free.&lt;/p&gt;

&lt;p&gt;I've found myself also starting development sessions by reviewing what Copilot sent me on something between work sessions, which can be a great way of getting into the flow of something - or take care of the more tedious aspects of software engineering while letting me focus on the strategic direction or specific concerns I care about.&lt;/p&gt;

&lt;h3&gt;
  
  
  How coding agents might impact hiring
&lt;/h3&gt;

&lt;p&gt;If you look at the prior section, you'll see that a lot of what I'm doing is more senior or supervisory in nature versus directly authoring code. While I'm still writing code and enjoying it, I'm finding that the code I'm writing is less boilerplate or trivial in nature and more specialized and strategic.&lt;/p&gt;

&lt;p&gt;This is good, but not all developers can do this. You need a certain level of experience to be able to guide other developers and evaluate their code effectively, and this holds up well for AI.&lt;/p&gt;

&lt;p&gt;Because of this, I view AI as filling similar roles to more junior team members: executing on well-defined and standardized tasks that can be easily communicated.&lt;/p&gt;

&lt;p&gt;While AI doesn't &lt;em&gt;replace&lt;/em&gt; the more junior team members in your organization, it does rival them in some ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI agents are becoming increasingly able to get unstuck on their own&lt;/li&gt;
&lt;li&gt;Copilot has the breadth of its training data available to it, so it likely knows libraries junior devs don't yet&lt;/li&gt;
&lt;li&gt;Copilot works quickly, and can produce a lot of code very quickly, outpacing even senior developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, junior team members have a lot of value as well, and can do things that AI can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A greater degree of domain knowledge in your organization, its products, its data, and its business context&lt;/li&gt;
&lt;li&gt;Effectively consider the end user and the context in which code operates&lt;/li&gt;
&lt;li&gt;Human level problem-solving, common sense, and decision-making&lt;/li&gt;
&lt;li&gt;The ability to debug problems or scenarios related to specific data situations&lt;/li&gt;
&lt;li&gt;The tendency to grow and become senior engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good junior developer is going to provide far more value to an organization than a solid AI agent is able to, but I can see the temptation for organizations to rely on AI agents instead of junior developers, and this scares me for our industry and the many talented people already struggling to get a foot in the door.&lt;/p&gt;

&lt;p&gt;Ultimately, if you're an organization that has busy senior engineers and code already present on GitHub, Coding Agents are something that you should try out and see how it impacts your workflow and productivity. Just be cautious because although Coding Agent is usually cheaper than a junior engineer's salary, it's not a replacement for talented, growing, flexible, and human engineers on your team.&lt;/p&gt;

&lt;p&gt;I think at this point, it's best to talk about where AI agents fall short in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI agents fall short
&lt;/h2&gt;

&lt;p&gt;In conversations about AI and particularly about AI productivity there's a central truth that is often overlooked: &lt;strong&gt;Most of software engineering isn't about writing code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I've been writing code for the vast majority of my life, and over two decades professionally. While a lot of my job is around writing code, that code only comes after:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding the business need or current inadequate behavior&lt;/li&gt;
&lt;li&gt;Determining what an ideal solution should do&lt;/li&gt;
&lt;li&gt;Identifying several different ways of achieving this goal&lt;/li&gt;
&lt;li&gt;Selecting a leading candidate for implementation - often with collaboration from others who understand other areas and other needs&lt;/li&gt;
&lt;li&gt;Identifying places in code that will need to be adjusted to support the change&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Making code changes to support the new behavior&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ensuring the code works&lt;/li&gt;
&lt;li&gt;Thinking of ways the code might break, edge cases that we might not have considered, etc. then making sure the code works for those ways as well.&lt;/li&gt;
&lt;li&gt;Ensuring the code is as secure, testable, and performant as we expected it to be when we selected our candidate solution&lt;/li&gt;
&lt;li&gt;Communicating the change in documentation and to others.&lt;/li&gt;
&lt;li&gt;Ensure the change flows through the processes for feedback, testing, and deployment to various environments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, code changes are only a small portion of software engineering, yet we pay an inordinate amount of attention to them when we think about AI productivity solutions and even when we consider using offshore development resources.&lt;/p&gt;

&lt;p&gt;While I believe AI systems can already perform some of these steps to some degree or another, we tend to evaluate their effectiveness mostly on authoring new content, which is a strength area for AI systems. However, humans have strong skills across all of these areas and knowing what to change and the implications of different approaches along with how this fits into the existing data and application architecture are critically important pieces of software engineering.&lt;/p&gt;

&lt;p&gt;Also keep in mind that in modern software engineering a change is often needed across multiple different services and databases. While an AI agent might be able to handle changes in one place, they might find themselves less equipped to know all the services that need to change and make the requisite changes for those areas.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI and Human Partnership
&lt;/h3&gt;

&lt;p&gt;Because of the complexity of software engineering and the relative strengths and weaknesses of AI and humans, I think that AI agents and AI tooling are best deployed for targeted tasks that have been thought through by an experienced engineer.&lt;/p&gt;

&lt;p&gt;An ideal flow might be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Engineers vet an organization's needs and determine a series of technical changes needed to support the new goals&lt;/li&gt;
&lt;li&gt;These individual changes are written up as work items and either assigned to other engineers or assigned to AI agents&lt;/li&gt;
&lt;li&gt;AI agents or engineers work on the change and send a draft pull request on for review&lt;/li&gt;
&lt;li&gt;The change is manually tested and verified by another engineer who uses it as a starting point for the final pull request&lt;/li&gt;
&lt;li&gt;The developer makes additional improvements, changes, and tests to support the pull request and ensure it fully meets the organization's needs&lt;/li&gt;
&lt;li&gt;The PR is marked ready for review&lt;/li&gt;
&lt;li&gt;Other developers review the PR, familiarize themselves with the changes, and leave comments&lt;/li&gt;
&lt;li&gt;The change eventually merges into the main branch and reaches production, where it will be supported by a team that understands the changes and designed the approach.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where software engineering may be headed with AI Copilots
&lt;/h2&gt;

&lt;p&gt;AI agents like GitHub Copilot are powerful and destined to change how organizations and engineers hire and work.&lt;/p&gt;

&lt;p&gt;I believe that software engineers can focus on the big picture, stay oriented around technical changes going on in their systems, but use AI to do the majority of the work on well-defined tasks the engineers define, then customize the final behavior of those changes.&lt;/p&gt;

&lt;p&gt;Not every change will benefit from AI, and some more sensitive pieces of work reveal new things to think about with each line of code that needs to be modified or added, but a strategic deployment of AI can help busy engineers remain productive between meetings and optimize their time on a busy schedule.&lt;/p&gt;

&lt;p&gt;I also hope that the emergence of AI as skilled solutions implementers will help focus experienced and new software engineers on other core competencies that are uniquely theirs: domain knowledge, communications skills, past experiences, empathy for users and business stakeholders, and the ability to evaluate a number of different plans and possible implementations and select the one that is right for the business today and where they're going tomorrow.&lt;/p&gt;

&lt;p&gt;AI is advancing at a tremendous rate and being an efficient, experienced, well-rounded, and adaptable engineer is more important than ever, but I'm glad to have copilots along for the ride as we build new things together.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
    <item>
      <title>Aspire Roadmap 2025: Code-first DevOps, polyglot, and AI</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Fri, 01 Aug 2025 12:43:33 +0000</pubDate>
      <link>https://dev.to/leading-edje/aspire-roadmap-2025-code-first-devops-polyglot-and-ai-4h8g</link>
      <guid>https://dev.to/leading-edje/aspire-roadmap-2025-code-first-devops-polyglot-and-ai-4h8g</guid>
      <description>&lt;p&gt;The Aspire team has recently published their &lt;a href="https://github.com/dotnet/aspire/discussions/10644" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt;, revealing an exciting evolution from local development orchestration to a comprehensive framework for DevOps concerns. &lt;a href="https://victorfrye.com/blog/posts/hello-aspire-breaking-down-key-features" rel="noopener noreferrer"&gt;Aspire&lt;/a&gt; launched with a code-first application model and instantaneous run experience, then expanded into deploy scenarios with publishers. This roadmap shows how it's becoming a complete code-first alternative to YAML-heavy DevOps toolchains while embracing polyglot development and AI workload orchestration.&lt;/p&gt;

&lt;p&gt;While these are aspirational goals rather than firm commitments, they provide valuable insight into Aspire's direction. Let's explore the most compelling features and why they position Aspire as a game-changing DevOps framework for .NET, polyglot, and AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code-first DevOps
&lt;/h2&gt;

&lt;p&gt;DevOps combines development (Dev) and operations (Ops) to deliver software faster and with higher quality. While DevOps is fundamentally about people and processes, the technology and tooling often involve tedious YAML configuration files for CI/CD pipelines and infrastructure management. Aspire is changing this by providing a code-first approach to local development, testing, and deployment, replacing configuration complexity with familiar programming languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local development
&lt;/h3&gt;

&lt;p&gt;Aspire already excels at code-first application modeling in C#, expressing your entire architecture—databases, services, .NET projects, and polyglot components—then spinning it up locally with &lt;code&gt;aspire run&lt;/code&gt;. No YAML configuration files, just standard .NET code that ideally mirrors your production architecture. The roadmap expands this with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved container support&lt;/strong&gt;: Shell commands, scripts, and interactive debugging inside containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-repo support&lt;/strong&gt;: Native orchestration across multiple repositories
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in runtime acquisition&lt;/strong&gt;: Automatic installation of Node.js, .NET, and other required runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aspire local development is a mature feature set already. These improvements focus on further simplifying the developer experience and tackling complex orchestration scenarios. Multi-repo support has been a long-standing pain point as many developers opt to separate components, like a frontend and backend, in separate repositories. Removing the monorepo requirement or custom cross-repo orchestration makes Aspire more accessible to many teams. You can already run polyglot applications in containers with Aspire, but continued improvements will allow more robust debugging and feedback loops with local containerized applications. The built-in runtime acquisition is both the most exciting and most daunting feature here. It may simplify the first run experience, which helps with onboarding and CI/CD pipelines and one area that I adore of Aspire. However, depending on its implementation, it could also lead to extra local machine complexity with Aspire managed runtimes versus system-wide runtimes. The local development experience is already fantastic and delivers the code-first developer experience Aspire promises. Therefore, I am optimistic that these improvements will build on that foundation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Aspire's code-first model and instant run experience create ideal conditions for integration and end-to-end testing. You can spin up your entire application stack locally, creating an instant integration test environment with minimal friction. The &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package provides this test host for xUnit and other testing frameworks and allows you to benefit from Aspire features like intelligent resource state notifications that eliminate arbitrary sleep times in tests. The roadmap adds advanced testing capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partial app host execution&lt;/strong&gt;: Run only specific components in tests to reduce overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request redirection and mocking&lt;/strong&gt;: Control traffic between components for chaos engineering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code coverage support&lt;/strong&gt;: Coverage collection and reporting for integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where local development is the mature foundation of Aspire, testing is currently a secondary benefit that often surprises users by revealing the true value of the framework. These improvements take the Aspire testing story to the next level. Aspire goes from being the startup tooling that manages your integration testing components to a chaos engineering and middleware validation powerhouse. The partial app host execution isn't limited to testing and reduces overhead in local development scenarios where certain components are not needed. In testing, this partial execution may allow each test to receive the benefit so API integrations can be isolated without starting up the frontend or further broken down to individual microservices that matter. Coupled together with request redirection and mocking of components, you could create test scenarios that simulate real-world failures between integrations and validate chaos behavior. Imagine chaos testing your application before you even deploy it from your machine with the same ease of unit testing. The code coverage support is the extra bonus reward: get code coverage metrics for your integration and chaos tests in a way that is often limited to unit tests? Yes, please! The roadmap suggests the current Aspire testing story is only in its infancy, and these improvements will make it a reason to adopt Aspire for testing alone if they materialize as envisioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Deployment bridges development and customer value delivery. Aspire's local orchestration model naturally extends to cloud deployment scenarios. Aspire has been expanding to include deployment targets and publishers, simplifying the process of getting your application into production.&lt;br&gt;
Currently, Aspire publishes artifacts like Bicep, Docker Compose, and Kubernetes manifests. You can deploy any Aspire resource the same way you would without Aspire, but with it you get seamless delivery to deployment targets like Azure Container Apps. While deployment targets are limited and opinionated, the roadmap addresses key enterprise needs that are still missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Additional deployment targets&lt;/strong&gt;: Support for Azure App Service, Azure Functions, and improved Docker/Kubernetes workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment support&lt;/strong&gt;: Define dev/stage/prod environments with specific configurations and secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipeline generation&lt;/strong&gt;: Auto-generate GitHub Actions, Azure DevOps, and GitLab pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment is an emerging focus in the Aspire story. Azure Container Apps is the first focus for deployment target and flexible as a hosting platform, but it's not flexible enough for all enterprise scenarios even within corporate environments invested in Azure. The roadmap as expected promises more common Azure deployment targets for traditional .NET workloads like Azure App Service and Azure Functions, but it is still lacking in amazing &lt;a href="https://victorfrye.com/blog/posts/reviewing-aspirejs#deployment-targets" rel="noopener noreferrer"&gt;polyglot deployment targets like Azure Static Web Apps&lt;/a&gt;. Environment support is critical for enterprise adoption as the majority of enterprises host multiple environments. DevOps practices may push us for consistency between environments, but there are always differences in configuration and secrets to isolate environments. The CI/CD pipeline generation in addition to environment support delivers on the idea of code-first DevOps: define your environments and application model in code, then generate the necessary pipelines to deploy it based on your code-first model. The overall deployment story is still evolving, but the question that will persist is whether Aspire can provide enough flexibility to meet the diverse needs of enterprises' existing applications. These features are a step in that direction. I hope the Aspire team delivers, and we see Aspire become a code-first framework for continuous delivery and deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polyglot aspirations
&lt;/h2&gt;

&lt;p&gt;Aspire is not just a .NET framework; it is a polyglot orchestration framework that allows you to model and run conjoined applications in various languages. .NET, JavaScript, Python, and more are all supported, but the only first-class experience is in .NET projects. With the app host authored in C#, the service defaults project providing .NET best practices, and NuGet client integrations for simplifying configuration in your application code, Aspire is an amazing .NET developer experience. You can &lt;a href="https://victorfrye.com/blog/posts/reviewing-aspirejs" rel="noopener noreferrer"&gt;host JavaScript&lt;/a&gt; and Python applications, but you don't get the same level of integration and tooling. The roadmap reveals the Aspire team's ambition to provide a first-class polyglot experience that extends beyond .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Uniform client integrations&lt;/strong&gt;: Connection strings, configuration, and telemetry work consistently with new language support via npm (JavaScript) and pip (Python) packages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates and samples&lt;/strong&gt;: Quickstarts and documentation for C#, JavaScript, and Python&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language app host&lt;/strong&gt;: Experimental WebAssembly support for multiple runtimes in a single process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The polyglot aspirations of Aspire are focusing on JavaScript and Python support first. The uniform client integrations with npm packages for JavaScript and other languages will get us closer to parity with the .NET experience. Improved documentation and more polyglot samples will also help as figuring out how to use Aspire currently relies on developers doing the translation between C# and other languages themselves. Technically a hosting integration, but if Aspire supports the &lt;code&gt;Aspire.Hosting.Testing&lt;/code&gt; package in JavaScript I would be ecstatic. Documentation and packages together could elevate the polyglot experience to make Aspire stand out beyond traditional .NET developers. It may invite more developers to experiment with the .NET platform beyond Aspire as well.&lt;/p&gt;

&lt;p&gt;The cross-language app host is a fascinating item and the one I find hardest to envision myself. Will this be a way to write the app host without .NET? Will it wrap all the runtimes in a single process on your computer? What will it actually look like? The roadmap tells us it is experimental, so it may never materialize, or it may be something we start to see soon. I will be watching this closely as it starts to take shape and the value becomes clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Artificial intelligence
&lt;/h2&gt;

&lt;p&gt;While AI dominates software conversations, Aspire has focused on fundamental developer experience improvements rather than AI-first features. As AI applications continue to be mainstream, Aspire is positioned to apply its orchestration strengths to AI workloads. The roadmap outlines several AI-specific features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token usage visualization&lt;/strong&gt;: Real-time token counts, latency, and evaluation metadata in the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-specific metrics&lt;/strong&gt;: Native support for generative AI telemetry, including model name, temperature, and function call traces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure AI Foundry&lt;/strong&gt;: Integration for building agent-based applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspire MCP server&lt;/strong&gt;: Optional runtime endpoint exposing the Aspire model as an MCP server for AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building AI applications is itself a nascent discipline. The Aspire team appears to be taking a measured approach to AI integration instead of branding itself another set of AI-native tools. These Aspire AI features are focused on two key areas: observability and agents. Observability is another area Aspire already excels at with the Aspire dashboard. Token usage and LLM-specific metrics visualizations in the Aspire dashboard will be a wonderful addition to the existing telemetry and observability features. It stays true to the natural value of Aspire while also extending to needs of AI local development needs.&lt;/p&gt;

&lt;p&gt;In agentic regards, Aspire works but has a lot of limitations. Existing AI integrations, like Azure OpenAI and Ollama, provide some options for local and cloud-hosted LLMs. The integration with Azure AI Foundry may extend the catalog and options for LLMs. It will be exceptionally interesting if the integration supports Azure AI Foundry Local capabilities to provide a unified catalog of models both locally and in the cloud. The Aspire MCP server likewise adds agentic capabilities to Aspire. Model Context Protocol (MCP) is becoming an industry standard for AI agents communicating, understanding, and interacting with outside systems. An Aspire MCP server could provide development tools like GitHub Copilot with deep context on your application model and all the resources Aspire manages. I am all for more intelligent development workflows. Like so many other technologies, Aspire is targeting AI trends and trying to provide its own value in the space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aspire tooling
&lt;/h2&gt;

&lt;p&gt;As Aspire evolves into a mature framework, its tooling ecosystem continues expanding beyond the core .NET SDK. The roadmap includes several improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aspire CLI&lt;/strong&gt;: Continued improvements and unified commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WinGet and Homebrew installers&lt;/strong&gt;: Standard install support for Windows and macOS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extension&lt;/strong&gt;: Run, debug, and orchestrate polyglot Aspire applications in VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tooling of Aspire is a meta story and so are its roadmap items. The code-first DevOps value and the polyglot aspirations, they all deliver on a core premise of Aspire: a simplified developer experience. When the tooling to setup Aspire or interact with it isn't easy, the core premise is lost. The Aspire CLI has already started this meta story with my favorite command, &lt;code&gt;aspire run&lt;/code&gt;, which provides a consistent way to run your Aspire hosted applications locally. Continued improvements to the CLI and other commands will help make it easier to adopt and utilize Aspire. The WinGet and Homebrew installers are similar in value and may simplify installing the Aspire CLI which is already more complex than it should be. Finally, the VS Code extension may help deliver on the polyglot aspirations of Aspire by making development with Aspire more accessible to the tools JavaScript and Python developers already use without relying on CLI knowledge. Sure, CLI commands mean you can do it today but installing the Aspire CLI and generating projects requires a &lt;a href="https://victorfrye.com/blog/posts/adding-aspire-cli-guide" rel="noopener noreferrer"&gt;guide of the right CLI commands&lt;/a&gt;. Overall, the meta story of these tools is to simplify using Aspire so that Aspire can simplify your developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/dotnet/aspire/discussions/10644" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt; that the Aspire team published is an exciting glimpse into a rapidly evolving framework. Nothing is a commitment, but the vision tells a story of what Aspire is developing into: a code-first DevOps framework that simplifies local development, testing, and deployment while embracing polyglot development and AI orchestration. I am incredibly excited by this roadmap as it aligns with my own dreams for Aspire. I love what it is today and recommend it to every .NET developer and some polyglot developers. If the Aspire team can deliver on half of these features, it will only continue to be a game-changer for developing distributed applications.&lt;/p&gt;

&lt;p&gt;Let me know what you think of Aspire and where it is going. Are you excited about the roadmap? Do you think Aspire can deliver on these promises? I would love to hear your thoughts and experiences with Aspire so far.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>javascript</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Reviewing Aspire.JS: Current state of Aspire for JavaScript</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Fri, 01 Aug 2025 12:42:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/reviewing-aspirejs-current-state-of-aspire-for-javascript-cpm</link>
      <guid>https://dev.to/leading-edje/reviewing-aspirejs-current-state-of-aspire-for-javascript-cpm</guid>
      <description>&lt;p&gt;Aspire is the coolest thing in software development right now. That's a statement I frequently make, but it comes from a place of genuine excitement for this nascent framework that is transforming how we can model, run, and deploy applications. Local development with Aspire is effortless regardless of the complexity of your architecture. Aspire is a part of the .NET platform, but it extends past .NET to provide polyglot orchestration for JavaScript, Python, and other languages.&lt;/p&gt;

&lt;p&gt;One reason I refer to Aspire as the coolest thing in software development is my frequent use of it in JavaScript projects. Whether it's a simple static site or a full-stack application, Aspire has become my go-to tool for local development.&lt;/p&gt;

&lt;p&gt;This post is a review of Aspire for JavaScript including current state, my personal experiences, and future aspirations. If you are a JavaScript, .NET, or polyglot developer interested in Aspire, this analysis is for you. If you are not familiar with Aspire, you may want to read a &lt;a href="https://victorfrye.com/blog/posts/hello-aspire-breaking-down-key-features" rel="noopener noreferrer"&gt;breakdown of its key features&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;Aspire in its current state is a code-first orchestration framework written in C# but enabling local development and hosting of polyglot applications. JavaScript and .NET exist in harmony with Aspire. Given the common stack of a JavaScript web frontend, a .NET web API backend, and a containerized database or other backing services, Aspire hosts the entire stack and abstracts away the different mechanisms for running and connecting each component.&lt;/p&gt;

&lt;p&gt;The above stack is the apparent assumption of Aspire for JavaScript. Aspire allows for modeling and running JavaScript backends, full-stack JavaScript applications, and scripts, but given the .NET-first nature of Aspire, you will be writing some C# code if you use Aspire. Accepting this for the app host, the orchestrator and C# model, Aspire provides two extension points of note for JavaScript development: Integration packages and deployment targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrations packages
&lt;/h3&gt;

&lt;p&gt;Integration packages are the libraries that extend Aspire to support the various projects, executables, containers, and services that make up your application. These packages are hosted as NuGet packages and can either be hosting or client integrations. Hosting integrations extend the app host of Aspire to model and run components like a JavaScript web app. Client integrations are libraries that allow you to consume the Aspire configurations and defaults to connect to the hosted components. However, client integrations are exclusive to .NET projects given they are packed as NuGet packages.&lt;/p&gt;

&lt;p&gt;This still leaves a variety of hosting integrations for JavaScript development to benefit from. The following are some of the most relevant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.NodeJs" rel="noopener noreferrer"&gt;Aspire.Hosting.NodeJs&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Node.js applications via node or npm scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bun" rel="noopener noreferrer"&gt;CommunityToolkit.Aspire.Hosting.Bun&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Bun applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.Deno" rel="noopener noreferrer"&gt;CommunityToolkit.Aspire.Hosting.Deno&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Deno applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.PostgreSQL" rel="noopener noreferrer"&gt;Aspire.Hosting.PostgreSQL&lt;/a&gt;&lt;/strong&gt;: Provides hosting for PostgreSQL database via &lt;a href="https://hub.docker.com/_/postgres" rel="noopener noreferrer"&gt;Docker Hub registry postgres images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.MongoDB" rel="noopener noreferrer"&gt;Aspire.Hosting.MongoDB&lt;/a&gt;&lt;/strong&gt;: Provides hosting for MongoDB database via &lt;a href="https://hub.docker.com/_/mongo" rel="noopener noreferrer"&gt;Docker Hub registry mongo images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Redis" rel="noopener noreferrer"&gt;Aspire.Hosting.Redis&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Redis via &lt;a href="https://hub.docker.com/_/redis" rel="noopener noreferrer"&gt;Docker Hub registry redis images&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Azure.Storage" rel="noopener noreferrer"&gt;Aspire.Hosting.Azure.Storage&lt;/a&gt;&lt;/strong&gt;: Provides hosting for Microsoft Azure cloud storage services, including Blob, Queue, Table, and Azurite emulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nuget.org/packages/Aspire.Hosting.Testing" rel="noopener noreferrer"&gt;Aspire.Hosting.Testing&lt;/a&gt;&lt;/strong&gt;: Provides a test host for .NET unit testing frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the packages provide flexibility of hosting JavaScript apps with the Node.js, Bun, or Deno runtimes. Databases and cloud service hosting include popular JavaScript data solutions like PostgreSQL, MongoDB, Redis, and Azure Blob Storage. Together, Aspire still provides the same local development experience for JavaScript application as it does for .NET with instantaneous runs and abstractions over other configuration files. The major deficits are you must minimally write C# code for the Aspire app host and to consume the Aspire host for testing, you will need to write integration tests using .NET testing frameworks like xUnit. For true polyglot developers familiar with both JavaScript and .NET, this is a non-issue that provides all the benefits of Aspire with the flexibility of using JavaScript for what JavaScript is best at. However, for a JavaScript only developer, there are extra barriers to entry here that Aspire has yet to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment targets
&lt;/h3&gt;

&lt;p&gt;Deployment is an emerging focus in the Aspire story. Applications orchestrated with Aspire can be deployed anywhere the same way you would deploy without Aspire, however the focus here is deployment through Aspire. Aspire is expanding to include publishers and deployment targets, taking your modeled application and using it to generate artifacts like Bicep and container images. Given Aspire's origins in .NET and Microsoft solutions, the initial deployment targets are opinionated and limited. By default, the easiest deployment target is &lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/overview" rel="noopener noreferrer"&gt;Azure Container Apps&lt;/a&gt;, a serverless platform for running containerized applications. However, there is a fundamental flaw here for JavaScript developers: hosting with Azure Containers Apps assumes you need a server.&lt;/p&gt;

&lt;p&gt;JavaScript developers are accustomed to a true serverless experience, one in which the web browser is the host environment. Frameworks like Next.js allow for server-side computation, but many JavaScript frameworks and applications are designed to run entirely in the browser using a bundle of JavaScript, HTML, and CSS. This has a lot of benefits for developers, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No server management&lt;/strong&gt;: No need to manage servers or containers, just a static file host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant scaling&lt;/strong&gt;: Static files can be served from a CDN, scaling automatically with demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower costs&lt;/strong&gt;: Static file hosting is often cheaper than running containers or VMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And so much more. This is antithetical to traditional .NET development and represents a fundamental difference in JavaScript versus .NET. Some JavaScript developers may opt for containerized hosting due to enterprise infrastructure or for self-managed static web servers like Nginx, but Azure already provides a first-class static web hosting solution with &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/overview" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;. Azure Static Web Apps are nowhere to be found in the Aspire deployment story, which is a major gap for Aspire for JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Aspire.JS story
&lt;/h2&gt;

&lt;p&gt;To understand how Aspire fits JavaScript development currently and potentially in the future, it is helpful to understand how a developer who has adopted Aspire already uses it. I am a full-stack developer who currently favors .NET for backend development, React for frontend development, and Azure for cloud hosting. I started using Aspire for a sample .NET web API that I wanted to run on macOS and Windows, so that anyone could pull down the code and run it with minimal configuration. Aspire was perfect for this, so I started using it for all my .NET projects. This in turn led me to use Aspire to host a React frontend alongside my web API and database, which also proved to be effortless. Finally, I asked the question: &lt;em&gt;Why not use Aspire for my JavaScript only projects?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have 3 static sites that I maintain, including my personal blog, and I wanted to use Aspire for local development to provide a consistent local development experience across all my projects. It works. Every personal project, including live websites or demo applications, is locally orchestrated with Aspire by default. I also recommend it for any enterprise .NET project I work on. However, the barrier of recommendation stops at projects that do not include .NET components currently. Aspire is currently an excellent choice for .NET and polyglot projects that include .NET, but the benefits of Aspire for JavaScript only or polyglot projects without .NET are not an easy sell. The C# app host, NuGet client integrations, and lack of polyglot deployment targets that do not align are all barriers to entry for JavaScript developers.&lt;/p&gt;

&lt;p&gt;The C# app host is a non-issue for me as a .NET developer, but for any project not already using .NET, it means extra SDKs to install and a new language to learn. Admittedly, the app host is not complex C# code until you start creating your own custom components. It is the download of the .NET SDK that is the high barrier. The NuGet client integrations are less a barrier and more a missing feature to sell the value story. Finally, deployment targets are a nascent feature in a nascent framework. I started using Aspire without its deployment features due to feature immaturity. To this day, I favor Azure Container Apps or Azure Functions for .NET workloads and Azure Static Web Apps for JavaScript workloads and handle deployment separately from Aspire. Together, this means the Aspire story for non-.NET applications adds .NET as a development dependency and is missing client integrations and deployment flexibility I would expect for recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future aspirations
&lt;/h2&gt;

&lt;p&gt;Aspire recently published their &lt;a href="https://victorfrye.com/blog/posts/aspire-roadmap-2025" rel="noopener noreferrer"&gt;2025 roadmap&lt;/a&gt;, which includes several features that may solve the current limitations of Aspire for JavaScript. The most exciting are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot client integrations&lt;/strong&gt;: Connection strings, configuration, and telemetry work consistently via npm packages as they do with existing NuGet packages for .NET projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates and samples&lt;/strong&gt;: More documentation and quickstart examples for JavaScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-language app host&lt;/strong&gt;: An experimental WebAssembly app host that may reduce .NET friction for JavaScript developers authoring the Aspire app host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features may further make Aspire an accessible choice for JavaScript developers and provide some of the .NET exclusive benefits for JavaScript components in your applications. The npm client integration packages excite me the most as a polyglot developer because it would allow me to integrate databases and cloud services like Azure Storage into my JavaScript components with the same reduced configuration as Aspire is doing for .NET projects. This adds parity in developer experience and closes the gap for recommendation and adoption of Aspire for JavaScript development. Documentation improvements are also always welcome and ease adoption. The cross-language app host is interesting, but I am still unsure of what it may amount to or if it'll even materialize. If it does, maybe it removes the .NET SDK download as a barrier. These features are directional and not commitments but provide a hope for increased parity with Aspire .NET developer experience.&lt;/p&gt;

&lt;p&gt;The remaining gap is deployment. This is an emerging area and the .NET story itself is still evolving for deploying with Aspire. However, I am actively watching for how this matures and the targets that get first-class support. If static hosting targets like Azure Static Web Apps are added, Aspire for JavaScript becomes a much more compelling recommendation. If Aspire only provides first-class support for traditionally .NET hosting targets like Azure App Service, Azure Functions, and Azure Container Apps, then the polyglot story remains incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final assessment
&lt;/h2&gt;

&lt;p&gt;Aspire is the coolest thing in software development right now and is actively evolving. For polyglot developers familiar with .NET, Aspire is a game-changer and you should experiment with adding it yourself. However, for JavaScript development and polyglot applications without .NET, there are still barriers to entry that prevent Aspire from being a compelling recommendation. Can you do it? Absolutely. Do I use Aspire for JavaScript development? Yes. Do I recommend it for JavaScript only projects? Not yet. But maybe in the future. Maybe soon.&lt;/p&gt;

</description>
      <category>aspire</category>
      <category>dotnet</category>
      <category>javascript</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>dotnet run file.cs: The new file-based application model</title>
      <dc:creator>Victor Frye</dc:creator>
      <pubDate>Wed, 02 Jul 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/leading-edje/dotnet-run-filecs-the-new-file-based-application-model-1d24</link>
      <guid>https://dev.to/leading-edje/dotnet-run-filecs-the-new-file-based-application-model-1d24</guid>
      <description>&lt;p&gt;I missed something at &lt;a href="https://victorfrye.com/blog/posts/microsoft-build-2025-wrapped" rel="noopener noreferrer"&gt;Microsoft Build 2025&lt;/a&gt;: the announcement of the new &lt;code&gt;dotnet run file.cs&lt;/code&gt; model in &lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-10-preview-4/" rel="noopener noreferrer"&gt;.NET 10 Preview 4&lt;/a&gt;. This is a new paradigm for running and writing .NET applications and if you are reading this, you might not be the target of this feature. However, you will probably meet or read C# code that is written this way.&lt;/p&gt;

&lt;p&gt;This article will explore the new feature of &lt;code&gt;dotnet run file.cs&lt;/code&gt; and the value it brings to the .NET ecosystem. Run it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Project-Based Model
&lt;/h2&gt;

&lt;p&gt;Today, if I wanted to write a simple C# console application that output "Hello, World!", I need to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the .NET SDK.&lt;/li&gt;
&lt;li&gt;Install an IDE or text editor like Visual Studio or Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Create a new .NET project using the IDE or the &lt;code&gt;dotnet new&lt;/code&gt; CLI command.&lt;/li&gt;
&lt;li&gt;Write my code in the &lt;code&gt;Program.cs&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is changing, or at least the steps. However, the output of this today is as given the command: &lt;code&gt;dotnet new console --name HelloWorld&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File actions would have been taken:
  Create: ./HelloWorld.csproj
  Create: ./Program.cs

Processing post-creation actions...
Action would have been taken automatically:
   Restore NuGet packages required by this project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is the dry run output of the &lt;code&gt;dotnet new&lt;/code&gt; command. Notice two files are created: &lt;code&gt;HelloWorld.csproj&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;. The &lt;code&gt;csproj&lt;/code&gt; file is an XML file that contains information any .NET developer is all too familiar with. The &lt;code&gt;Program.cs&lt;/code&gt; file is where I write my code. Additionally, you will quickly see &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories created and start populating as you write and publish your application. Do you know what both directories are for, even today? Do you find XML friendly to read? Microsoft asked a new question: Is this all overwhelming for someone new?&lt;/p&gt;

&lt;p&gt;The keyword above was &lt;strong&gt;new&lt;/strong&gt;. I invite you to recall your days learning to code and suppress your experienced instincts. When I do, I remember sitting in a classroom feeling like I might never understand programming and would fail. C# was my first language. We have bootcamps, universities, and online courses in excess to help new developers. That is working, but they are learning JavaScript or Python. Why? Because the onboarding experience is easier. The barrier to entry lower.&lt;/p&gt;

&lt;p&gt;What if this changed? Introducing the new &lt;code&gt;dotnet run file.cs&lt;/code&gt; paradigm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New File-Based Model
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dotnet run&lt;/code&gt; we keep discussing is the Dotnet CLI command any .NET command-line user is familiar with. However, the &lt;code&gt;file.cs&lt;/code&gt; is in reference to a new single file-based application model. That means in our steps from earlier, we change them to the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the .NET SDK.&lt;/li&gt;
&lt;li&gt;Install an IDE or text editor like Visual Studio Code.&lt;/li&gt;
&lt;li&gt;Create a new C# file, e.g. &lt;code&gt;hello.cs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Write my code in the &lt;code&gt;hello.cs&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The steps are incredibly similar but also simplified. You need the .NET SDK and a tool for writing code still, but you no longer need to understand a complex project generation process and only have one file to manage. Let's review it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;!/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;property&lt;/span&gt; &lt;span class="n"&gt;AssemblyName&lt;/span&gt; &lt;span class="n"&gt;VictorFrye&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HelloWorld&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. I could link to a repository, but if you copy and paste this you get a complete .NET application you can run. There is no &lt;code&gt;csproj&lt;/code&gt; file, and &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories are not created in your working directory. And if you run the command &lt;code&gt;dotnet run hello.cs&lt;/code&gt;, you get an active Kestrel web server that responds with "Hello World!". The latter half of the code is top-level statements, a feature not so new. However, the first three lines are special.&lt;/p&gt;

&lt;p&gt;The first line is a shebang: a Unix convention that tells the system how to execute the file. In this case, it tells the system to use the &lt;code&gt;dotnet run&lt;/code&gt; command to execute the file. With this new paradigm, you must have the .NET SDK installed and Dotnet CLI available still. A shebang is not required, but it does enable running the file without explicitly calling &lt;code&gt;dotnet run&lt;/code&gt; on Unix-like systems. This is cool, but mostly just a convenience.&lt;/p&gt;

&lt;p&gt;The second and third lines are new directives. You may be using directives in your code today, such as &lt;code&gt;#if DEBUG&lt;/code&gt; or &lt;code&gt;#region Feature X&lt;/code&gt;. However, the new &lt;code&gt;#:&lt;/code&gt; directives are unique to the run file paradigm. The &lt;code&gt;.csproj&lt;/code&gt; file normally tells our .NET application critical information like SDKs, MSBuild properties, or NuGet packages to use. The run file paradigm still supports these, but instead you use a &lt;code&gt;#:sdk&lt;/code&gt; directive or &lt;code&gt;#:property&lt;/code&gt; directive. In this case, I'm using the &lt;code&gt;Microsoft.NET.Sdk.Web&lt;/code&gt; SDK to pull in ASP.NET Core features for web APIs and setting the assembly name to &lt;code&gt;VictorFrye.HelloWorld&lt;/code&gt; because I like my name. These new directives are only for the run file paradigm, and you will get warnings if you try to use them in a traditional project model.&lt;/p&gt;

&lt;p&gt;Behind the scenes, everything is still there. The project file still exists but is virtual and interpreted by the Dotnet CLI. The &lt;code&gt;obj&lt;/code&gt; and &lt;code&gt;bin&lt;/code&gt; directories are created, but in a temporary location that is abstracted away. The application is still built and run like any other .NET application. The difference is in the simplicity of authoring C#. However, when the project reaches maturity or someone is ready to take it to the next level, they can convert the file-based application to a traditional project-based application. All you must do is run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet project convert hello.cs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Value Added
&lt;/h2&gt;

&lt;p&gt;I am really excited about the &lt;code&gt;dotnet run file.cs&lt;/code&gt;. The primary users targeted are new developers. This is a win if Microsoft succeeds and more developers embrace modern .NET applications. Some might be concerned about not learning all the details of the full project-based application model, but new developers learning .NET mean a larger .NET community, new libraries, and more innovation in the ecosystem. This is a huge win for the .NET developer community.&lt;/p&gt;

&lt;p&gt;However, the value added doesn't stop there. File-based applications are also great for scripts and small utility apps. You don't need a folder structure or a csproj file. You can now write a couple C# scripts to help you maintain your existing codebase or automate tasks. This is a huge win for scripting capabilities and reducing project overhead.&lt;/p&gt;

&lt;p&gt;Another use-case is one you might have to read yourself: .NET samples. Sample applications are used by libraries to showcase how to use specific features or APIs. They are also used by conference speakers and at meetups to illustrate concepts or provide live demos of features. In this article itself, I would normally have to create a full project to demonstrate the feature, and I would link the repository so a reader could copy it exactly and reference it or run it themselves. Now, I can provide the entire sample in a code block that is easy to copy and paste. This is a huge win for documentation and sample authors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limitations So Far
&lt;/h2&gt;

&lt;p&gt;Right now, file-based applications are limited to a single file. They are also unsupported in Visual Studio, favoring Visual Studio Code as a more likely editor for targeted users. Finally, it is only in .NET 10 Preview versions at the moment. It will not be until November 2025 that we see the first general availability release of file-based applications and likely time after before we see new developers learning in this form or a C# scripting revolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concluding Remarks
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dotnet run file.cs&lt;/code&gt; paradigm is a new way to write and run .NET applications. It may or may not be for you, but the goal is a more inclusive and accessible .NET ecosystem. The best outcome is more developers learning and using .NET. Maybe C# scripts take off and we see C# become the new Python. Maybe documentation and sample applications get less verbose. The future is hard to predict, but I am hopeful for a future where I see file-based C# applications in the wild.&lt;/p&gt;

</description>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
