<?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>Forem: Veith Röthlingshöfer</title>
    <description>The latest articles on Forem by Veith Röthlingshöfer (@veith-octomind).</description>
    <link>https://forem.com/veith-octomind</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%2Fuser%2Fprofile_image%2F1238586%2Ffc989e41-1187-4916-954b-38cab37bc2f7.png</url>
      <title>Forem: Veith Röthlingshöfer</title>
      <link>https://forem.com/veith-octomind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/veith-octomind"/>
    <language>en</language>
    <item>
      <title>Why agents DO NOT write most of our code - a reality check</title>
      <dc:creator>Veith Röthlingshöfer</dc:creator>
      <pubDate>Mon, 03 Nov 2025 09:38:49 +0000</pubDate>
      <link>https://forem.com/veith-octomind/why-agents-do-not-write-most-of-our-code-a-reality-check-87j</link>
      <guid>https://forem.com/veith-octomind/why-agents-do-not-write-most-of-our-code-a-reality-check-87j</guid>
      <description>&lt;p&gt;At Octomind, we build AI agents - but our code is still mostly written by humans. We love LLMs and use them everywhere we can, from our product to our internal workflows. But despite all the hype, we’re nowhere near “agents writing most of our code.”&lt;/p&gt;

&lt;p&gt;We have good reasons for not jumping on the bandwagon of companies like &lt;a href="https://youtu.be/zDmW5hJPsvQ?t=1100" rel="noopener noreferrer"&gt;Anthropic (generating 80%)&lt;/a&gt;, &lt;a href="https://www.cnbc.com/2025/04/29/satya-nadella-says-as-much-as-30percent-of-microsoft-code-is-written-by-ai.html" rel="noopener noreferrer"&gt;Microsoft (30%)&lt;/a&gt; or &lt;a href="https://www.forbes.com/sites/jackkelly/2024/11/01/ai-code-and-the-future-of-software-engineers/" rel="noopener noreferrer"&gt;Google (25%)&lt;/a&gt; just yet.&lt;br&gt;&lt;br&gt;
‍&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%2Fmhtp5yj7kd868l4myn5m.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%2Fmhtp5yj7kd868l4myn5m.png" alt="a linkedin screenshot" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*one of many, source: &lt;a href="https://www.linkedin.com/posts/rhunterharris_your-best-developers-should-be-leading-not-activity-7379856503962497025-Wv9A/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few vital things are “not there” yet. Here’s why it matters - and what it takes to actually close the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimenting with coding agents in day to day coding
&lt;/h2&gt;

&lt;p&gt;We’ve been using &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Claude Code&lt;/strong&gt;, and &lt;strong&gt;Windsurf&lt;/strong&gt; for months, but none of us can honestly say they’ve boosted our productivity in a meaningful way (say, 20% or more). Sure, tab completions are often solid, and I’ve had some success getting them to generate unit tests - especially when there's existing tests to copy from (like new routes).&lt;/p&gt;

&lt;p&gt;Still, that’s nowhere near the 80%+ efficiency gains others claim. So, driven by equal parts FOMO and curiosity, my colleague Fabio and I decided to spend the past week implementing a roadmap feature &lt;em&gt;entirely&lt;/em&gt; with AI.&lt;/p&gt;

&lt;p&gt;Before diving in, we combed through the documentation for our preferred tools to make sure we weren’t missing anything useful. We also updated our Cursor rules and &lt;a href="http://claude.md/" rel="noopener noreferrer"&gt;CLAUDE.md&lt;/a&gt; file to inject  updated knowledge about our product and our dev workflow, enabled &lt;strong&gt;BugBot&lt;/strong&gt; for AI code reviews and went to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature we tried to build (with AI)
&lt;/h2&gt;

&lt;p&gt;At Octomind, we build an agent-powered end-to-end testing platform. Our tests aren’t tied to branches - they live centrally in our system, which doesn’t support branch-specific versions of test cases. That works fine until you start using branch deployments.&lt;/p&gt;

&lt;p&gt;Picture a SaaS app with three tests: login, create post, edit post. The app under test is developed with branch deployments for each pull request (PR). Now imagine a branch that changes the login flow - say, it adds 2FA. The existing login test (which only checks username + password) will now fail, blocking the pipeline for that PR.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;p&gt;At the moment, you’ve got two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remove the failing test so it doesn’t block unrelated PRs, fix it manually (or via AI) to handle the new flow, merge, then re-enable it.&lt;/li&gt;
&lt;li&gt;Update the test directly and merge your PR - but now every other dev’s pipeline breaks until you’re done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither is great. One blocks others; the other breaks trust in your merge.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;p&gt;To combat this, we wanted to extend the concept of branches to our tests. When a branch is created, you can spawn a branch-specific copy of a test. That copy runs only for that branch and can be edited freely. When the branch merges, the copy becomes the new default.&lt;/p&gt;

&lt;p&gt;We figured this feature should be doable in about a week with two developers.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;h2&gt;
  
  
  First try: Running wild
&lt;/h2&gt;

&lt;p&gt;As the first iteration, we let the agents roam. We did not expect this to work perfectly, but we wanted to see where it’s at.&lt;/p&gt;

&lt;p&gt;We’ve got a decent-sized monorepo, so “just dump everything into context” isn’t an option. We take testing seriously and have guardrails the AI can use to check its own output. &lt;/p&gt;

&lt;p&gt;So I wrote a detailed brief and attached the files it needed into the context. This wasn’t a ‘tiny prompt performing miracle’ thing - I iterated the prompt until it was as specific as possible. Within ~5 minutes, the agent produced a plan with reasonable 11 TODOs: &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%2Ftb67j1iy76z5p1b5oc4s.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%2Ftb67j1iy76z5p1b5oc4s.png" alt="cursor agent produced a coding plan" width="703" height="402"&gt;&lt;/a&gt;&lt;br&gt;
*Cursor agent produced a coding plan&lt;br&gt;
‍&lt;/p&gt;

&lt;p&gt;We hit &lt;code&gt;run&lt;/code&gt; and that’s where it went sideways. The agent started cranking out lines, but stumbled on basics any dev would breeze through - like regenerating the Prisma client after a database schema change (yes, the Cursor rules spelled it out). &lt;/p&gt;

&lt;p&gt;I went back and forth with it several times for clarification. It reported success with the message: “The feature should now work correctly! The fork button should be functional, and you'll be able to see forked test cases using the new filter. 🎉”  while:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not checking off all the TODOs&lt;/li&gt;
&lt;li&gt;Producing nothing that worked on our dev server (which it had access to via computer use)&lt;/li&gt;
&lt;li&gt;Ignoring basic coding guidelines we explicitly listed
‍&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An incomplete list of misses that wouldn’t trip up a human dev here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built a React component for new buttons… and never wired it into existing components&lt;/li&gt;
&lt;li&gt;Skipped our standard logging library&lt;/li&gt;
&lt;li&gt;Used very inefficient database queries (it made an extra request for every ID that is joined anywhere)&lt;/li&gt;
&lt;li&gt;Ignored our naming and structure conventions&lt;/li&gt;
&lt;li&gt;Added two new external libs for trivial stuff we already have&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and I am not even talking about bugs here. These are just things that immediately stick out and would not have happened to a developer. - &lt;/p&gt;

&lt;p&gt;Yes, we tried multiple rounds of “still doesn’t work - you forgot to actually use the new button” and similar nudges. The result: a 2,000-line PR that needs review and rework almost everywhere.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;h2&gt;
  
  
  Take two: Smaller, incremental changes
&lt;/h2&gt;

&lt;p&gt;I decided to start over. We never expected this to just work - these agents are supposedly better at smaller features anyway. So I stepped back and thought through how I’d actually build it myself, step by step. This kind of ideation is where &lt;em&gt;I usually do like using an LLM&lt;/em&gt; - not to code, but to bounce around approaches in planning mode.&lt;br&gt;
‍&lt;/p&gt;

&lt;p&gt;Then I had it make a plan again, this time for just the first piece: &lt;strong&gt;loading the correct test from the database given an execution URL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It produced another long, seemingly sensible plan. I let it run - expecting to step in more often this time. After running through several contexts worth of chat, I had another pull request.&lt;/p&gt;

&lt;p&gt;About 1,200 lines of code, just for this one part. Does it work? Well… it typechecks. Beyond that, who knows.&lt;/p&gt;

&lt;p&gt;In the spirit of the experiment, I handed the PR to BugBot for review. It left four comments - one of them pointing out that transaction handling was broken. That’s not great for data consistency, so I fed the comment back into the chat.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;p&gt;The AI’s response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Of course, massive oversight on my part, I apologize. Let me fix it: …”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apology accepted, I guess. It did patch a few places, and when I asked if it saw any more issues, it replied:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Recommendation‍&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix Transaction Consistency: Ensure all database calls within getFullTestCasesWithPrerequisites use the same transaction client. (and … 7 more things)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;All of this was in a single file. The only context it needed was the database schema - literally the thing it was just working on.&lt;/p&gt;

&lt;p&gt;It also has a habit of making life easy for itself: &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%2Ftg4ne67v845ezl2gj9fx.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%2Ftg4ne67v845ezl2gj9fx.png" alt="a cursor coding agent response" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’ll check for linter errors, but only by running head -30 and some regex filters, so it convinces itself everything’s fine.&lt;br&gt;
‍&lt;/p&gt;

&lt;p&gt;Confidently marking half-done work as finished, apologizing for “massive oversights,” fixing things only to break others (look up the German word &lt;a href="https://www.reddit.com/r/German/comments/6pp1nb/word_of_the_day_verschlimmbessern/" rel="noopener noreferrer"&gt;verschlimmbessern&lt;/a&gt;), and completely ignoring our existing designs and UX is not even the worst part.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;h2&gt;
  
  
  The issues that really matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Loss of mental model
&lt;/h3&gt;

&lt;p&gt;Let’s say the agent can now ship medium-complexity features with little help. And let’s even assume we’ve fixed the “wait 3 minutes, review 1000 lines of output” problem by turning developers into orchestrators of agents instead of coders. That’s the dream many LinkedIn posts are selling.&lt;/p&gt;

&lt;p&gt;Even then, a huge issue remains: &lt;strong&gt;I lose my mental model of the codebase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Right now, I know how changing one part affects another, where bugs tend to hide, and how the data model behaves. That intuition disappears when the AI keeps dropping thousand-line PRs that might even get auto-merged. When a teammate does that, I can trust they made thoughtful trade-offs, and I’ll pick up context while reviewing or building on top of it. With AI, that learning loop is gone.&lt;/p&gt;

&lt;p&gt;So when a tough bug or edge-case feature comes up - the kind the AI still can’t handle - it feels like I’m seeing the codebase for the first time. Maybe I skimmed some reviews of features I know the AI can do by itself (unless I ran auto-review tools like CodeRabbit), but that’s nowhere near the understanding that comes from actually interacting with the code yourself.&lt;/p&gt;

&lt;p&gt;Until I can trust the AI completely, I need to keep my own mental model alive. Otherwise, &lt;strong&gt;every time I need to do something myself feels like joining a new company&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Absence of self-reflection
&lt;/h3&gt;

&lt;p&gt;AI is currently bad at judging its own performance. Constant replies like these are only annoying if you let them get to you, but boy, is it hard not to:&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%2Fiyadpkrfc61ms1omr5wz.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%2Fiyadpkrfc61ms1omr5wz.png" alt="a cursor coding agent response" width="800" height="157"&gt;&lt;/a&gt;&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%2Fgo0jn1lsehc7jacsciyh.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%2Fgo0jn1lsehc7jacsciyh.png" alt="a cursor coding agent response" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I asked it to do so before implementing the feature, to which it responded with: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;MODERATE CONFIDENCE&lt;/strong&gt; - I can implement this correctly, but with some caveats:&lt;br&gt;
&lt;strong&gt;Overall Assessment:&lt;/strong&gt; I can implement this task correctly, but it would require careful attention to detail, thorough testing, and potentially some clarification on the missing methods and business logic. The complexity is manageable but not trivial.‍&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds like a human engineer’s self-assessment, because the model is parroting human training data. The problem is that it shouldn’t use the training that is made by humans (at least until recently) to judge its own ability, because it is not a human. &lt;/p&gt;

&lt;p&gt;And that’s the core issue: the model has no concept of its limits. You only find out if it can do the task by letting it try. An intern can say, “I’ve never done this.” An LLM doing so is very unlikely.&lt;/p&gt;

&lt;p&gt;Worse, on our smaller follow-up task it graded itself even higher:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;COMPLEXITY: HIGH&lt;/strong&gt; - This is a substantial refactoring&lt;br&gt;
&lt;strong&gt;MY ABILITY: HIGH CONFIDENCE&lt;/strong&gt; - I can definitely implement this correctly because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The plan is very detailed and specific about what needs to be changed&lt;/li&gt;
&lt;li&gt;I understand the current architecture and data flow&lt;/li&gt;
&lt;li&gt;The changes follow established patterns in the codebase&lt;/li&gt;
&lt;li&gt;The implementation steps are clearly outlined&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task is definitely implementable and I have high confidence I can complete it correctly - it's just a matter of following the detailed plan step by step and implementing all the missing pieces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The good parts of coding agents
&lt;/h2&gt;

&lt;p&gt;AI absolutely has a place in a developer’s toolbox. I use ChatGPT or Cursor’s Ask mode daily - to brainstorm, debug, or get unstuck on smaller problems. Tab completions? They’re right about 80% of the time, which is good enough to keep them on. I even let AI handle things like writing unit tests for clean interfaces or refactoring small snippets. Wrapping a loop in  &lt;code&gt;Promise.allSettled&lt;/code&gt; is boring for me - but trivial and instant for AI. It’s also great at recreating well-known patterns from scratch - like traversing a tree structure. &lt;/p&gt;

&lt;p&gt;And for non-technical users, automation powered by AI can be a massive unlock. That’s literally what we work on at Octomind: automating a technical task, but within well-defined boundaries using specialized agents. They don’t write the whole codebase; they handle narrow, observable parts of it where output constraints keep them in check.&lt;/p&gt;

&lt;p&gt;Other focused tools can deliver similar value. And sure, maybe one day AI will truly handle everything it’s being credited with today (whether that’s LLMs or something beyond them).&lt;br&gt;
‍&lt;/p&gt;

&lt;p&gt;But we’re not there yet - and more people are starting to admit it.&lt;/p&gt;

&lt;p&gt;‍&lt;br&gt;
&lt;strong&gt;Veith Röthlingshöfer&lt;/strong&gt;&lt;br&gt;
AI engineer at Octomind&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop automating manual test cases</title>
      <dc:creator>Veith Röthlingshöfer</dc:creator>
      <pubDate>Thu, 16 Jan 2025 16:56:02 +0000</pubDate>
      <link>https://forem.com/veith-octomind/stop-automating-manual-test-cases-kjj</link>
      <guid>https://forem.com/veith-octomind/stop-automating-manual-test-cases-kjj</guid>
      <description>&lt;p&gt;Those that have been watching the testing landscape for a while might remember the craze that occurred when test automation started going mainstream. Will testers lose their jobs? What happens when we automate everything?&lt;/p&gt;

&lt;p&gt;Looking back, those concerns sound almost funny. Years of test automation have shown that despite significant speed improvements, companies still combine manual QAs with test automation engineers to help shipping high quality products.&lt;br&gt;
‍&lt;br&gt;
The recent &lt;strong&gt;'shifting left'&lt;/strong&gt; trend - pushing testing and quality processes to the earlier stages of development - has increased the focus on test automation. Many companies insist on transforming former manual QA teams to test automation and equip everyone with automation skills. The goal is to automate everything that was previously done manually. While there’s a good argument for broadening the technical skills of QA teams, &lt;em&gt;there’s definitely more to test automation than simply creating scripts to automate test cases.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating test cases
&lt;/h2&gt;

&lt;p&gt;Let me state it clearly. Automating manual test cases 1:1 is a bad idea.&lt;/p&gt;

&lt;p&gt;This idea usually stems from the desire to keep the same test coverage as when tests are performed manually. Simply put, the idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One test case = one test script&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But this does not meet with the reality of what test automation really is. Manual checks are not only a series of steps but a series of various qualitative assertions such as visual look of the application under test, exploratory side quests, prompting developers for additional contents and little experiments. Any tester worth their salt will not mindlessly perform a series of test steps and report back only when they’re unable to perform the next one. That’s what makes it more valuable than a test script. On the other side, there is much value in test automation that is worth pursuing.&lt;br&gt;
‍&lt;br&gt;
Test automation makes QA much faster and also more reliable. You can run test automation at any time and more frequently. Since it is automated you can also include the less important tests with any issues and this way gain more confidence. It’s also a lot cheaper and can run outside working hours.&lt;/p&gt;

&lt;p&gt;It requires a different approach than simply scripting scenarios though. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to automate tests
&lt;/h2&gt;

&lt;p&gt;Whenever a tester runs a manual check, they log in and have a broad goal of what needs to be done. There are no broad goals when it comes to test automation. A testing script needs a precise goal, precise result and precise set of steps. Without those, an end-to-end test will become overly complex, with way too many conditions altering the end result.&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%2Fa5l9jwufvipq9ggq6rs8.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%2Fa5l9jwufvipq9ggq6rs8.png" alt="manual vs automated testing visual concept " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that test automation needs to be more streamlined. It’s typically a good practice to follow some kind of pattern, so that there’s a standard to be followed for the whole test automation team. One of the good ones is the AAA pattern, which follows a set structure:&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%2F721pi5lb9y52mc2z53zs.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%2F721pi5lb9y52mc2z53zs.png" alt="arrange - act - assert - 3 phase concept of test automation" width="134" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This structure divides a test automation script into three distinct parts, each fulfilling a different objective.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Arrange&lt;/code&gt; is a test preparation phase. It has the goal of making sure that the data is properly seeded, the application under test is in a proper state and the context is clearly set. Typically, the arrange phase can be something like a login, creating some test data or simply opening the proper subpage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Act&lt;/code&gt; is the heart of the automation script. It’s a series of steps, closest to what the test case might be. This phase is responsible for getting the application from state A to state B. The series of steps are implicitly asserting that the functionality of the application under test is unbroken and allows the user to use the app’s functionality. In a to do app, this would be creating, editing or deleting a to-do item.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Assert&lt;/code&gt; is the most important step of a test. Without an assertion, the script is merely a series of steps. Assertions are an explicit confirmation that the functionality of the application under test works. In a to-do app, assertion might make sure that a to-do item is visible after it was created, no longer visible after it was deleted and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual vs. automated
&lt;/h2&gt;

&lt;p&gt;You could probably see some resemblance of the AAA pattern in manual testing. Even in manual testing there’s preparation, exploring and assessment. But as we stated earlier, that does not mean that simply automating a test case is going to work well.&lt;/p&gt;

&lt;p&gt;Let’s demonstrate this in an example. The following is a cookie message that appears on a page:&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%2Fo2zbp8h8zvsrli1pdoxn.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%2Fo2zbp8h8zvsrli1pdoxn.png" alt="cookie banner conceptual visual" width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a message that can appear seemingly randomly on a page. If testing this message is not within the scope, then the best course of action is to simply click “accept all” when the cookie consent message appears and move on.&lt;/p&gt;

&lt;p&gt;In test automation this poses a challenge. The message might be covering a portion of the page and we need to get it out of the way when it appears. Test automation scripts usually run in a clean browser which means that we will see this message more often than when testing manually. This might be aided with a condition in the test script. In pseudo code it might 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%2F07rn74wxju0ig4b0vpg5.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%2F07rn74wxju0ig4b0vpg5.png" alt="test script condition for a cookie test" width="500" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are shaking your head at this, I have to tell you that I have seen this approach many times. This little helper introduces a problem into the test automation suite. The script closes the cookie message anytime it runs, but what if the cookie message never appears, even when it should? And how do we test a case when we want the message to appear?&lt;/p&gt;

&lt;p&gt;The key to making a good test automation decision is to have decent technical knowledge. Digging a little bit deeper into the cookie message functionality reveals that when we click the “accept all” button, a setting (in the form of a cookie) is saved into the browser storage. This ensures that we don’t see the message on the same page over and over 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%2Fy5l58z4fhwbwpe6qz3j7.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%2Fy5l58z4fhwbwpe6qz3j7.png" alt="cookie banner visual concept 2" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is now a functionality we can test! Even better, it’s a functionality that we can control. Since most test automation frameworks always open browser with storage cleaned up, we can control when the cookie message appears by choosing when to inject the consent cookie into the browser.&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%2Fa91r48ckbhkpygveu9ia.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%2Fa91r48ckbhkpygveu9ia.png" alt="conceptual graph showing the concept of 3 phases of test automation" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Making good test automation decisions
&lt;/h2&gt;

&lt;p&gt;There’s a very popular idea in the world of software development known as DRY - don’t repeat yourself. It’s a simple principle stating that whenever you have a piece of code that needs to be used at multiple places, it should not be repeated, but rather abstracted. Many testers apply this principle to their test automation code.&lt;/p&gt;

&lt;p&gt;Let’s again demonstrate this in a simple example.&lt;/p&gt;

&lt;p&gt;Imagine that there are 500 end-to-end tests written for an application that requires users to log in. For the sake of simplicity we’ll assume that every one of these 500 tests require the user to be logged in.&lt;/p&gt;

&lt;p&gt;Applying the DRY principle to our test code means that we are going to create a login function that will get called at the beginning of each test as the “arrange” section.&lt;/p&gt;

&lt;p&gt;And while the code is being abstracted, executing the login sequence in 500 tests could mean that simply logging in will take 25 minutes of the whole test run (given that login sequence takes 3 seconds). For some tests this can create a bit of imbalance in how long each part of the test takes. After all, the reason why we write tests is more connected to the act and the assertion phases.&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%2Fs2krw1y434j1ptbm7qay.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%2Fs2krw1y434j1ptbm7qay.png" alt="3 phase test automation stages with dominant " width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The good news is that this draws a clear picture on what we need to focus on if we want to optimize.&lt;/p&gt;

&lt;p&gt;In this case, we can use the same principle as we did with our cookies. It’s pretty much the same idea as when checking the “remember me” box when logging in to a page. But instead of using it to automatically log in to your favourite social media, you can simulate the same behavior in your tests.&lt;/p&gt;

&lt;p&gt;In principle, this approach has the goal of creating relevant contexts and then re-using them. Instead of starting each test from scratch, we want to reuse contexts in as many tests as we can. Login is a great candidate for this, because login does not change from test to test and it’s also rarely affected by other test phases.&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%2F4vnp5xlaautlo44zuze4.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%2F4vnp5xlaautlo44zuze4.png" alt="visual representation of test automation optimization, 3 phase concept" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modern test automation tools such as &lt;strong&gt;Playwright&lt;/strong&gt; and &lt;strong&gt;Cypress&lt;/strong&gt; already have tools for caching login sessions. This approach is not yet widely adopted, but it is the best way of dealing with applications that use login. &lt;a href="https://www.octomind.dev/" rel="noopener noreferrer"&gt;Octomind&lt;/a&gt; provides an option to set up a shared authentication state as well as native support for one-time passwords.&lt;/p&gt;

&lt;p&gt;This not only optimizes the test performance, but reduces the amount of login attempts. This helps tremendously when testing applications that have rate-limiting or captcha protection against brute force attacks. These can sometimes be a significant hurdle when it comes to test automation.&lt;br&gt;
‍&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving test actions
&lt;/h2&gt;

&lt;p&gt;Another area of improvement is the “act” section of the test. While it’s not the obvious first candidate, there’s a lot of potential in making tests faster and therefore making the test automation worth it.&lt;/p&gt;

&lt;p&gt;There’s a lot that happens in the “act” phase when doing e2e testing. It’s a good practice to ask whether parts of this phase are being reused across tests. Let’s say you are testing a to-do app. There’s a good chance that you need to create an initial todo item (or more) to meet your testing goal. This can potentially bloat your “act” phase.&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%2Ftmm2e5o1xrg1mrwfsvlv.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%2Ftmm2e5o1xrg1mrwfsvlv.png" alt="3 phases of test automation of to-do app with 'to do creation' included in the 'act' phase" width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usually when following the DRY principle, testers abstract creation of todo items into its own function. And this is a good thing to do, because now an action can be reused in multiple tests.&lt;/p&gt;

&lt;p&gt;conceptual graph showing repeated necessity of 'creating todos' steps in testing to-do app&lt;br&gt;
But if you stop and think about it, isn’t creating todo items the part of our test that is responsible for arranging the test?&lt;/p&gt;

&lt;p&gt;It definitely is!&lt;/p&gt;

&lt;p&gt;This means that if we want to draw our graph correctly, it should look more 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%2Fgx9hef5xbxno63c82ti6.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%2Fgx9hef5xbxno63c82ti6.png" alt="3 phases of test automation of to-do app with 'to do creation' included in the 'arrange' phase" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It now seems that we have once again made the arrange part of our test too big. But this is once again a situation where we can apply the same principle as with login. Once we understand the parts that create the setup of our tests, we can simply abstract them and reuse and optimize them across different tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test chaining and dependency structure
&lt;/h2&gt;

&lt;p&gt;Chaining tests using dependencies is a best practice to structure test automation efficiently. In an automated approach, each test case represents a small task in the user flow you want to achieve. Testing an entire user flow is the execution of a sequence of test cases chained together. &lt;/p&gt;

&lt;p&gt;Every test case is executed only once which reduces test runtime significantly. It follows the DRY principle allowing for easier test maintenance. Separation minimizes the number of test code adjustments when the code in your app changes. This is valid for both, hand-written tests in &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; or &lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; and autogenerated tests by &lt;a href="https://octomind.dev/docs/" rel="noopener noreferrer"&gt;Octomind&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Dependency structure provides you and your collaborators with a better oversight when your test suite gets bigger. It gets harder to understand what each test is doing or how much of your app is actually covered over time.&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%2F7kxyh40zac762vqksqcz.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%2F7kxyh40zac762vqksqcz.png" alt="dependency tree test arrangement in Octomind testing tool, screenshot" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test cases vs. test scripts
&lt;/h2&gt;

&lt;p&gt;This brings us to the final difference between manual testing with test cases and test script automation. Usually, when test cases are put together, they follow a pattern of user behavior. These behaviors are then added to groups, so that testing is done within a scope of given features and functionalities. Test scripts, while they should mimic user behavior, do not need to follow the same grouping. The grouping can be done based on the setup each test needs.&lt;/p&gt;

&lt;p&gt;---‍&lt;/p&gt;

&lt;p&gt;Testing is highly analytical work and requires a good knowledge of the system under test. Test automation requires it all, but also requires some analytical work on what needs to be broken down in order to run tests in an optimal way. Many times, the optimization process is like a mathematics equation, where you can subtract parts that are redundant. A manual approach with test cases is full of these parts and the art of effective test automation decision lies in identifying them.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Prompt engineering your boss</title>
      <dc:creator>Veith Röthlingshöfer</dc:creator>
      <pubDate>Thu, 21 Dec 2023 17:29:55 +0000</pubDate>
      <link>https://forem.com/veith-octomind/prompt-engineering-your-boss-39a4</link>
      <guid>https://forem.com/veith-octomind/prompt-engineering-your-boss-39a4</guid>
      <description>&lt;p&gt;Our designer started playing around with &lt;strong&gt;Midjourney&lt;/strong&gt; to generate caricature stickers of our team in Octomind’s app trademark colors during one of his breaks. These were intended to be a fun addition to our Slackmojis, FigJam stickers, and &lt;a href="https://graphite.dev/docs/key-features#customizable-meme-library" rel="noopener noreferrer"&gt;Graphite’s meme collection&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;We usually lean into the useful side of generative AI when working on our &lt;a href="https://app.octomind.dev/setup/url" rel="noopener noreferrer"&gt;AI-powered UI testing tool&lt;/a&gt;. But hey, it's the festive season, so why not have some fun with it and share it with you guys.  &lt;/p&gt;

&lt;p&gt;(DISCLAIMER: At Octomind, we pride ourselves on having a friendly work environment where we get along pretty informally. This might not be the ideal Christmas present for your boss!)&lt;/p&gt;

&lt;p&gt;Here are a few examples to give you an idea of what I’m talking about:&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%2Faig1s9dtxpjsfhb6zcbd.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%2Faig1s9dtxpjsfhb6zcbd.png" alt="sticker example" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
‍&lt;br&gt;
Our designer would upload a reference image and provide small prompts like &lt;code&gt;&amp;lt;image link&amp;gt; &amp;lt;core emotion&amp;gt;, cartoon sticker style, mint green and dark lavender&lt;/code&gt;. He would then iterate through numerous attempts until he arrived at something that looks about right. Some of the iterations were downright terrifying, but that's all part of the process. &lt;/p&gt;

&lt;p&gt;‍I, for one, went through several iterations before arriving at a sticker that somewhat(?) resembles me. It created gems like these:&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%2Fcf27z33dh0wq3ql1kp7k.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%2Fcf27z33dh0wq3ql1kp7k.png" alt="AI generated imagery" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Side note:&lt;/strong&gt;&lt;/em&gt; Midjourney has a penchant for generating plants and other random items in the background. I suppose it's because the embedding distance from "lavender" to "plant" is quite short.&lt;/p&gt;

&lt;p&gt;All in all, it’s been a lot of fun, so we thought our boss, the co-founder &amp;amp; CTO, might enjoy it too. &lt;/p&gt;

&lt;p&gt;What do you say, Daniel R.? Are you up for some caricature stickers of yourself?&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%2Fmt9ob5m2hizoqzawzxis.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%2Fmt9ob5m2hizoqzawzxis.jpg" alt="photo of a man" width="250" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He turned out to be a tough nut to crack. Whatever Midjourney generated, it never resembled him at all. Daniel became known as ‘the Unmidjourneyable’.  &lt;/p&gt;

&lt;p&gt;After playing around with it for a bit on my train ride back home from our Christmas party I found a workflow that produced decent results. It took several tools and little tricks I’ll share with you here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Midjourney vs. Dall-E 3
&lt;/h2&gt;

&lt;p&gt;We use Midjourney to generate octopus imagery within our brand style. The typical Midjourney style is usually getting us closer to visual consistency. &lt;strong&gt;OpenAI’s Dall-E 3&lt;/strong&gt; is, however, a lot better at understanding complex prompts than Midjourney is. So I knew that I would probably want to start there. &lt;/p&gt;

&lt;p&gt;Simply combining the image and prompt in Dall-E wasn't producing the results I wanted. The model seemed to struggle with the requested "mint green and dark lavender color palette" more than Midjourney did.&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%2Fiuvww2hcxdmkf1muqqep.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%2Fiuvww2hcxdmkf1muqqep.png" alt="AI generated image" width="358" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also had to be a bit careful with my wording here. GPT doesn’t want you generating images of other people and stops the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Style transfer with Dall-E
&lt;/h2&gt;

&lt;p&gt;First, I needed a way to generate something in a similar style to what we want. We already have a few example stickers where Midjourney worked well (shout out to my colleague Fabian’s face for being very prompt-able!), and I already knew how from a private project.&lt;/p&gt;

&lt;p&gt;You can ask Dall-E to analyze an image and generate a detailed description of the &lt;strong&gt;art style&lt;/strong&gt;. Then, I used that description as a guideline for the art style in a secondary image generation step. This technique also allows you to keep the style relatively consistent between new chats and image generations!&lt;/p&gt;

&lt;p&gt;The prompt I used was:&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%2F42nluevawij43rtlcqqm.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%2F42nluevawij43rtlcqqm.png" alt="generated sticker example" width="292" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Me:&lt;/strong&gt; Describe the style of this image. Do not focus on the actual content, only on the style, colors and other artistic elements. Name some art styles and artists that fit this image. Be as detailed as possible.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Response:&lt;/strong&gt; The image uploaded features a caricature-style illustration with exaggerated facial features commonly found in modern caricature art. The subject's head is larger in comparison to the body, emphasizing the facial expression, which in this case is one of anger or annoyance. The use of bold, dark outlines to define the shapes is reminiscent of comic book art, particularly the styles found in graphic novels or mature-themed comics.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The colors are vivid but not overly bright, suggesting a more subdued palette that enhances the serious tone of the expression. The use of shading and highlights on the face gives the image a three-dimensional feel, adding to the impact of the expression.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This style of illustration might be influenced by artists like Mort Drucker or Tom Richmond, known for their work in MAD Magazine, where such exaggerated features and expressions are a hallmark. However, it also contains elements seen in the works of street artists who often employ bold lines and vivid colors to create impactful imagery.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In terms of art styles, it aligns with Pop Art in its boldness and accessibility, as well as with Expressionism in its focus on representing emotional experience rather than physical reality. The style does not fit directly into the classic fine art categories but has a contemporary edge that is often seen in digital art, graphic design, and illustration.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, read the response carefully and adjust it as desired. Dall-E sometimes sneaks in phrases you do not want to have in your style description. In this case, we want to use it as a style guide later, so I replaced &lt;code&gt;The image uploaded features […]&lt;/code&gt; with &lt;code&gt;The style should resemble […]&lt;/code&gt;. Additionally, I didn’t want Daniel’s sticker version to be angry, so I replaced &lt;code&gt;[...] in this case is one of anger or annoyance&lt;/code&gt; with &lt;code&gt;[...] in this case is one of hype and excitement&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I added some context to the following prompt template to generate our sticker:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;You are an expert in creating amazing caricature sticker designs based on a textual description of a person and a desired style. Generate a sticker based on the description: &amp;lt;add description&amp;gt; And the style: &amp;lt;add style&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I will refer to this as “the template” from now on. Next, I had to make sure that the sticker looks similar to our co-founder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playing police
&lt;/h2&gt;

&lt;p&gt;The only way to interact with either image generation model is through a textual description of what you want to see. Prompts like “make it look like the photo” don’t work that well as seen above, and I am not that great at describing a person’s looks that I would trust it to be an accurate and reproducible description. &lt;/p&gt;

&lt;p&gt;I thought: “Who is better at drawing someone based on a description than a police sketch artist?”&lt;/p&gt;

&lt;p&gt;Using the following prompt, I fed Daniel’s image to &lt;strong&gt;ChatGPT&lt;/strong&gt; to get a description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Me:&lt;/strong&gt; You are an expert police sketch artist. You know everything about describing people to recreate a face from a textual description. Generate the perfect description for this man. Go over each aspect that makes him unique, describing as many individual physical features as possible. Use terms that set each feature in relation to how that feature looks on other people. Do not focus on the background. Make sure you could recreate this man based on the description you provide.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Response:&lt;/strong&gt; The individual in the image is a male appearing to be in his mid-30s to early 40s. He has a fair complexion with a warm undertone, and his face shape can be classified as oval, with a slightly elongated chin that gives a hint of a heart-shaped contour. His hair is short and styled in a casual, slightly tousled manner, exhibiting a peppered ash brown color, indicative of the onset of graying primarily at the temples.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;His forehead is broad and visibly unlined, leading to well-arched, medium-thickness eyebrows that are neatly groomed, slightly lighter than his hair color, suggesting they may lighten further with age. His eyes are almond-shaped, with a noticeable palpebral fold, and the irises are a cool hue, possibly blue or grey, framed by average-length lashes that do not appear particularly thick.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The nose is straight with a narrow bridge and a rounded, average-sized nasal tip, showing no signs of deviation or prominent bumps. The nostrils are symmetrical and of moderate size. His cheeks are relatively flat, with a subtle hint of cheekbones just below the mid-eye level. The man has a gentle smile, which slightly elevates the cheeks without revealing the teeth. His lips are of medium thickness with the upper lip slightly thinner than the lower, and the lip color is a healthy pink, in harmony with his overall complexion.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;His ears are of average size, proportionate to his face, with the lobes attached and no prominent helix or antihelix features. His jawline is defined, though not sharply angular, leading to a well-proportioned chin that complements his face without being overly prominent.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;He wears no visible jewelry or piercings and his attire suggests a practical outdoors orientation - he's wearing a dark olive green hooded jacket with the brand "Columbia" visible on the right side of the chest in white lettering, indicating a preference for functional, perhaps outdoor or athletic clothing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The overall impression is of a man who takes care in his appearance without it being overly groomed or styled, presenting a natural and approachable demeanor.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, I used this description and the template to generate a first sticker version:&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%2Fne27r4nwygz7tlz27agi.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%2Fne27r4nwygz7tlz27agi.png" alt="AI generated image" width="327" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is already closer than anything we managed to do with the picture and a short prompt, but still not really recognizable. I’d need a feedback loop!&lt;/p&gt;

&lt;p&gt;Continuing in the chat where I generated the first description, I asked it to analyze the generated image, the photo and the description and update the description to clear up any misconceptions, but GPT-4 got confused about which image is which and just generated a new description for the generated image.&lt;/p&gt;

&lt;p&gt;I needed a new trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playing professor
&lt;/h2&gt;

&lt;p&gt;Actually, I’ll pull not only one, but two tricks out of my hat.&lt;/p&gt;

&lt;p&gt;It was very obvious that GPT is confusing which image I’m referencing at what point. I was referring to them as “the first” and “the second”, or “the photo” and “the illustration”, but that wasn’t working, especially over the course of several interactions.&lt;/p&gt;

&lt;p&gt;First hack was to combine the photo and the illustration into one image using the open-source image editing tool &lt;strong&gt;GIMP&lt;/strong&gt;.&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%2F0rkznrqbnwmjr72m9uyb.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%2F0rkznrqbnwmjr72m9uyb.jpg" alt="original and its AI generated illustration combined in one picture" width="491" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, I could refer to them as “the photo on the left” and “the illustration on the right”, which really seems to reduce the confusion.&lt;/p&gt;

&lt;p&gt;The second trick is to adjust the context of who's describing the image and generating the prompt. A police sketch artist may be great at drawing a person based on a description, but is not necessarily good at generating that description in the first place. So I turned it into a very critical professor instead, which produced a way more detailed result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Me:&lt;/strong&gt; You are an expert police sketch artist. You are currently giving a lecture on how to describe people so that a police sketch artist can accurately reproduce their face. To teach people, you are letting them perform the following task: "Write a description of a photo of a man that I have given you. Another police sketch artist will draw the man based on your description. I will criticize and improve your description after we have seen the resulting illustration". You are now criticizing the description one of your students generated. The description he generated is: .&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Response:&lt;/strong&gt; The description provided by the student is quite detailed and paints a clear picture of the individual in question. However, to refine it for increased accuracy in a police sketch, we can focus on a few key areas:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Facial Structure:&lt;/strong&gt; The description mentions an oval face shape with a hint of a heart-shaped contour due to an elongated chin. To improve, we could specify the ratio of forehead to lower face to better capture the balance of facial features.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Eyes:&lt;/strong&gt; While 'almond-shaped' is a good start, we could provide the relative distance between the eyes and the exact shape and orientation of the eyelids.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Nose:&lt;/strong&gt; Instead of 'straight with a narrow bridge,' we might describe the exact length of the nose in relation to the width of the face and the shape of the nasal tip more precisely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Mouth and Smile:&lt;/strong&gt; The description of the smile is good, but the shape of the mouth when smiling can vastly change the perception of the face. Is the smile wide, does it extend beyond the eye line, etc.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Hair:&lt;/strong&gt; More specifics on the distribution of graying and the texture of the hair would be helpful.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Complexion:&lt;/strong&gt; We can clarify 'fair with a warm undertone' by giving examples or comparing with common complexion types.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Clothing:&lt;/strong&gt; The description is adequate, but the position of the jacket zipper, the hood, and how the clothing sits on the body could help.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here's an improved description: ... a slightly better description ...&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is generally a good idea to let LLMs break down the task into several steps. In this response, it did provide some very good criticism, but didn’t actually apply it in the improved description. The result using this description looked like this one below. The image was generated in the same chat as the previous version, without supplying the style instructions 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%2Fn0m16ohwl5zfddacz1e6.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%2Fn0m16ohwl5zfddacz1e6.png" alt="AI generated image" width="307" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, I asked it to apply the criticism and generate a more detailed description.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Response:&lt;/strong&gt; The subject is a Caucasian male estimated to be in his late 30s. His complexion is fair with a neutral undertone, leaning slightly towards a peachy warmth. The face shape is an even blend of oval with a subtle hint of heart-shaped, primarily due to the slightly wider forehead and a gently tapered chin which is softly rounded at the tip, avoiding sharpness.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;His hair is cut short, with a texture that suggests a thickness in the strands. The color is a rich ash brown with a prominent sprinkling of gray, more concentrated around the temples and blending into the natural color towards the crown. There is a subtle side parting on his left, and the hair follows a natural growth pattern with a slight elevation at the front, giving a casual, tousled appearance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The forehead is broad, occupying a third of the facial length, smooth, with no visible furrows or frown lines. His eyebrows are natural in shape with a soft arch, groomed but not sculpted, slightly lighter than the hair on his head, hinting at a sandy brown shade. They are of medium thickness, neither bushy nor sparse.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Eyes are of a size that balances with the other facial features, set at a moderate distance from each other. The shape is a classic almond, with upper eyelids having a prominent but not heavy palpebral fold. The eyes are reminiscent of the color of the summer sky, a clear blue with a grayish tint. The lashes are of average density and length, framing the eyes in a way that is noticeable but not striking.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The nose is well-proportioned to his face, straight, with a bridge that is of moderate width. The tip is rounded and slightly bulbous, which gives character without dominating his facial features. The nostrils are symmetrical and proportionate to the size of the nose.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;His cheeks are smooth, with a very subtle definition of cheekbones aligned with the center of the eyes. They do not protrude but give structure to his face. The cheeks transition into a gentle smile that lifts the area modestly, causing a small crinkle at the outer corners of the eyes, which adds a friendly warmth to his demeanor. His lips are naturally contoured, the upper lip being slightly thinner than the full, more pronounced lower lip. Both have a healthy blush pink tone, and the smile does not stretch broadly across the face but is enough to show a hint of dimples.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The ears are in proportion with the facial size, with the tops aligning with the brow line and the bottoms with the base of the nose. The lobes are attached, and there are no distinctive markings or piercings.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;His jawline is defined without being overly angular, leading to a chin that complements his oval face without becoming a focal point. There is a slight indentation below the lower lip, providing a subtle division between the chin and the rest of the face.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The attire, a dark olive green hooded jacket with a visible 'Columbia' logo on the right chest, suggests an affinity for outdoor wear. The jacket is worn in a relaxed manner, zipped to mid-chest, with the hood resting flat against the back. The fabric appears to be a durable, water-resistant material suitable for outdoor activities.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Overall, the man has a relaxed, congenial appearance, with well-maintained but not overly styled features, conveying an impression of practicality and an easygoing nature.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, this reads like something usable! Another good thing about this process is that you can iterate it until you are happy (someone should automate that &lt;em&gt;cough, cough&lt;/em&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The final push
&lt;/h2&gt;

&lt;p&gt;Using this last description, I got the following image from DALL-E:&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%2F0dugphxf8tqo92kj5g6z.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%2F0dugphxf8tqo92kj5g6z.png" alt="AI generated image" width="307" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It looks relatively recognizable, but is not as exaggerated as the original sticker, and it also lacks the mint green and dark lavender color palette. And as it turns out, Daniel is Barney Stinson’s long lost half-brother! &lt;/p&gt;

&lt;p&gt;But we already know that Midjourney is quite good at molding images into caricatures and following a color scheme. Yet, the complexity of generating the sticker has been greatly reduced. &lt;/p&gt;

&lt;p&gt;At the beginning, it received a photo and needed to abstract it into a sticker, a caricature, change the displayed emotions, change it to an illustration and switch to an unrealistic color palette. Now, it only needs to exaggerate the emotions and switch the color palette.&lt;/p&gt;

&lt;p&gt;And it did a great job! Feeding this image in Midjourney resulted in what I was going for.&lt;/p&gt;

&lt;p&gt;I used the prompt &lt;code&gt;&amp;lt;image url&amp;gt; exaggerated hype and excitement, mint green and dark lavender color palette, sticker design, caricature --iw 1.75&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I played with the image weight parameter –iw a bit, and found that 1.75 strikes a balance between sticking to the source material (--iw 2) and allowing the changes I described in the text prompt (--iw 0).&lt;/p&gt;

&lt;p&gt;I also found that it helps to not only specify “mint green and dark lavender”, but to explicitly add the word “color palette”, which significantly reduces the amount of random plants in the background.&lt;/p&gt;

&lt;p&gt;The only thing left to do is to cut out the sticker from the background with GIMP. Even if the background is not uniformly colored, it’s pretty easy thanks to the white outline of the person. &lt;/p&gt;

&lt;p&gt;Voilà!&lt;/p&gt;

&lt;p&gt;I added a little quote for more pizzazz. &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%2F2mqar4d3wjpz0n8f3d13.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%2F2mqar4d3wjpz0n8f3d13.png" alt="final result AI generated sticker" width="324" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt engineering your family
&lt;/h2&gt;

&lt;p&gt;With the holiday season approaching fast, I couldn't help but think this might be a unique gift idea. When I introduced the recipe to my team, one of my colleagues immediately began prompting her family for stickers as a Christmas gift.  &lt;/p&gt;

&lt;p&gt;Why not share this with people who supported Octomind in 2023?&lt;/p&gt;

&lt;p&gt;I created this blog to share with Octomind friends, users and fans. This is our special, genAI way of saying: thank you.&lt;/p&gt;

&lt;p&gt;Veith Röthlingshöfer&lt;br&gt;
ML engineer at Octomind&lt;/p&gt;

</description>
      <category>genai</category>
      <category>promptengineering</category>
    </item>
  </channel>
</rss>
