<?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: donerightlabs</title>
    <description>The latest articles on DEV Community by donerightlabs (@donerightlabs).</description>
    <link>https://dev.to/donerightlabs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112098%2F973c714e-2988-484c-a92d-d355072ac6f4.png</url>
      <title>DEV Community: donerightlabs</title>
      <link>https://dev.to/donerightlabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/donerightlabs"/>
    <language>en</language>
    <item>
      <title>Three Playwright/Apify bugs that took me way too long to find (and the fixes)</title>
      <dc:creator>donerightlabs</dc:creator>
      <pubDate>Sun, 06 Sep 2026 10:04:46 +0000</pubDate>
      <link>https://dev.to/donerightlabs/three-playwrightapify-bugs-that-took-me-way-too-long-to-find-and-the-fixes-15ko</link>
      <guid>https://dev.to/donerightlabs/three-playwrightapify-bugs-that-took-me-way-too-long-to-find-and-the-fixes-15ko</guid>
      <description>&lt;h1&gt;
  
  
  Three Playwright/Apify bugs that took me way too long to find (and the fixes)
&lt;/h1&gt;

&lt;p&gt;I recently shipped &lt;a href="https://apify.com/donerightlabs/tender-intelligence" rel="noopener noreferrer"&gt;Vietnam Tender Intelligence&lt;/a&gt; — a keyword-based monitor for Vietnam's national e-procurement portal, built with Playwright + &lt;a href="https://github.com/daijro/camoufox" rel="noopener noreferrer"&gt;Camoufox&lt;/a&gt; (a hardened Firefox build for stealth automation). It works, and it's live: it turned out to be a much better debugging exercise than I expected. Three bugs in particular cost me way more time than they should have, so I'm writing them down for whoever hits the same wall next.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A silent version mismatch between Playwright and Camoufox
&lt;/h2&gt;

&lt;p&gt;Right after wiring up the browser launch, every run failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found property "&amp;lt;root&amp;gt;.viewport.isMobile" - false which is not described in this scheme
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in my code touched &lt;code&gt;isMobile&lt;/code&gt;. Turned out Playwright 1.61 added that field to the internal &lt;code&gt;Browser.setDefaultViewport&lt;/code&gt; protocol call, and Camoufox's Juggler layer (the protocol it uses to drive Firefox) didn't recognize it yet. It's a real upstream incompatibility, tracked in &lt;a href="https://github.com/daijro/camoufox/issues/653" rel="noopener noreferrer"&gt;daijro/camoufox#653&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The counter-intuitive part: the official Apify base image is tagged &lt;code&gt;apify-python-playwright-camoufox:3.14-1.61.0&lt;/code&gt; — so the natural instinct is to match your &lt;code&gt;playwright&lt;/code&gt; pin to &lt;code&gt;1.61.0&lt;/code&gt;. Don't. That's the &lt;em&gt;broken&lt;/em&gt; version for this combo. Pinning &lt;code&gt;playwright==1.60.0&lt;/code&gt; fixed it immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; when a base image tag looks like a version recommendation, verify it against the actual compatibility matrix of the libraries involved — it can just be a taxonomy label, not a peer-dependency promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;.click()&lt;/code&gt; on a text input can hang forever; &lt;code&gt;.fill()&lt;/code&gt; doesn't
&lt;/h2&gt;

&lt;p&gt;This one took the longest to nail down because it was intermittent. Some runs would sail through in 40 seconds; others would sit at &lt;code&gt;Locator.click()&lt;/code&gt; until a timeout fired, with no error, no popup, nothing actionable in the trace.&lt;/p&gt;

&lt;p&gt;After enough repeated runs, I noticed the timeouts always stalled at the exact same call: clicking a text input immediately before typing into it — a completely standard "click to focus, then type" pattern. Switching to &lt;code&gt;locator.fill(value)&lt;/code&gt; — which focuses the element as part of its own actionability checks, without dispatching a raw mouse click — made the hang disappear across dozens of subsequent runs.&lt;/p&gt;

&lt;p&gt;I never found a fully satisfying root cause (my best guess: something in the click-dispatch path occasionally not resolving cleanly under a residential-proxy connection with irregular latency, which &lt;code&gt;fill()&lt;/code&gt;'s internal path avoids). But the fix generalizes: &lt;strong&gt;if you don't need the literal mouse-click side effect, &lt;code&gt;.fill()&lt;/code&gt; is strictly more robust for text inputs&lt;/strong&gt; — one less network round-trip and no dependency on precise coordinate/z-index resolution to succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Apify's automated Actor tests use your &lt;em&gt;default input&lt;/em&gt; — and an empty dataset counts as a failure
&lt;/h2&gt;

&lt;p&gt;Apify runs every published Actor daily with its default/prefilled input and expects: success, completion within 5 minutes, and a &lt;strong&gt;non-empty&lt;/strong&gt; dataset. Miss any of those on 2 of the last 3 daily runs and the Actor gets silently flagged "Under maintenance" (hidden from Store search) — you find out by email.&lt;/p&gt;

&lt;p&gt;Two non-obvious ways to trip this that aren't really "bugs" in the traditional sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your default input is a narrow query that occasionally has zero real-world matches, a perfectly correct run with zero results still counts as a failure. Pick a default keyword/filter broad enough to (almost) always return something.&lt;/li&gt;
&lt;li&gt;If you've added retry logic to make individual runs more resilient (good for real users, who don't mind an extra 2 minutes), make sure the worst case — including retries — still fits inside 5 minutes. A retry loop tuned for "eventually succeed" and a hard 5-minute CI-style SLA pull in different directions; you have to explicitly budget for both.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also: pick a genuinely persistent store, not the default one
&lt;/h2&gt;

&lt;p&gt;Small one, but it cost a day of "why isn't my session persisting" confusion: &lt;code&gt;Actor.getValue()&lt;/code&gt; / &lt;code&gt;Actor.setValue()&lt;/code&gt; operate on the &lt;em&gt;current run's&lt;/em&gt; default key-value store, which is fresh every run. To actually persist state (a saved cookie jar, in my case) across separate Actor executions, open a &lt;strong&gt;named&lt;/strong&gt; store instead — &lt;code&gt;Actor.openKeyValueStore(name=...)&lt;/code&gt; in Python — and read/write through that. Obvious in hindsight; not obvious from the method names.&lt;/p&gt;




&lt;p&gt;None of these are exotic. They're the kind of thing you find in 20 minutes with a good search — once you know the exact term to search for. Hopefully this saves the next person that 20 minutes, or the day I lost on the key-value store one.&lt;/p&gt;

&lt;p&gt;If you're building something similar (Playwright + Camoufox + Apify), happy to compare notes in the comments. And if you're curious what it looks like running end to end, the Actor itself is here: &lt;a href="https://apify.com/donerightlabs/tender-intelligence" rel="noopener noreferrer"&gt;Vietnam Tender Intelligence&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>debugging</category>
      <category>programming</category>
      <category>webscraping</category>
    </item>
  </channel>
</rss>
