<?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: Gary Parker</title>
    <description>The latest articles on DEV Community by Gary Parker (@garyparker).</description>
    <link>https://dev.to/garyparker</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%2F2027099%2Fab95a6b5-096a-4c83-ae96-279a3796a824.png</url>
      <title>DEV Community: Gary Parker</title>
      <link>https://dev.to/garyparker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/garyparker"/>
    <language>en</language>
    <item>
      <title>Playwright 1.65: A Filter That Matches Nothing Now Fails Every Shard</title>
      <dc:creator>Gary Parker</dc:creator>
      <pubDate>Sat, 14 Nov 2026 11:34:19 +0000</pubDate>
      <link>https://dev.to/garyparker/playwright-165-a-filter-that-matches-nothing-now-fails-every-shard-3gg</link>
      <guid>https://dev.to/garyparker/playwright-165-a-filter-that-matches-nothing-now-fails-every-shard-3gg</guid>
      <description>&lt;p&gt;Playwright 1.65 has one line in its notable changes that reads like a small tidy-up: a sharded run now fails with "No tests found" when no tests match across all shards. If your CI builds a &lt;code&gt;--grep&lt;/code&gt; or a path filter per pull request and fans it out over a shard matrix, that line decides whether a pull request that touches no tests is green or red.&lt;/p&gt;

&lt;p&gt;I ran the same suite on 1.64.1 and 1.65.1 to see exactly which cases changed. The harness and raw results are in &lt;a href="https://github.com/qa-gary-parker/poc-playwright-empty-shards" rel="noopener noreferrer"&gt;the PoC repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;32 spec files with one test each. The tests use no browser.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fullyParallel: true&lt;/code&gt; and &lt;code&gt;workers: 1&lt;/code&gt;, so Playwright can put each test on any shard.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@playwright/test&lt;/code&gt; 1.64.1 and 1.65.1, on Node 24 and macOS.&lt;/li&gt;
&lt;li&gt;0, 1, 2, 4, 8 and 16 selected tests, picked by tag.&lt;/li&gt;
&lt;li&gt;2, 4, 8 and 16 shards. Every shard is its own process, as it is in a CI matrix.&lt;/li&gt;
&lt;li&gt;Each cell with and without &lt;code&gt;--pass-with-no-tests&lt;/code&gt;, with Playwright's own split and with &lt;a href="https://garyparker.dev/projects/playwright-shard-balancer" rel="noopener noreferrer"&gt;playwright-shard-balancer&lt;/a&gt; in the config.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is 1,464 runs. I recorded the exit code of every run and the number of tests it ran. A second script runs four real filters that can match nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Zero selected tests, failed shards out of all shards:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--pass-with-no-tests&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Unsharded&lt;/th&gt;
&lt;th&gt;2 shards&lt;/th&gt;
&lt;th&gt;4 shards&lt;/th&gt;
&lt;th&gt;8 shards&lt;/th&gt;
&lt;th&gt;16 shards&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.64.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;1/1&lt;/td&gt;
&lt;td&gt;0/2&lt;/td&gt;
&lt;td&gt;0/4&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;td&gt;0/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.64.1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;0/1&lt;/td&gt;
&lt;td&gt;0/2&lt;/td&gt;
&lt;td&gt;0/4&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;td&gt;0/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.65.1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;1/1&lt;/td&gt;
&lt;td&gt;2/2&lt;/td&gt;
&lt;td&gt;4/4&lt;/td&gt;
&lt;td&gt;8/8&lt;/td&gt;
&lt;td&gt;16/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.65.1&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;0/1&lt;/td&gt;
&lt;td&gt;0/2&lt;/td&gt;
&lt;td&gt;0/4&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;td&gt;0/16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On 1.64.1, sharding hid the error. The unsharded run failed with "No tests found" and the same filter with &lt;code&gt;--shard&lt;/code&gt; passed on every shard. On 1.65.1 the sharded run agrees with the unsharded one: every shard fails.&lt;/p&gt;

&lt;p&gt;No other exit code changed. A job here is one cell of the matrix with all of its shards. With 1 to 16 selected tests, all 180 jobs had 0 failed shards on both versions, even when 15 of 16 shards had no test to run. An empty shard is still fine. A whole run with no tests is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which filters hit it
&lt;/h2&gt;

&lt;p&gt;The same check with four filters, as shards 1/2 and 2/2, without the flag:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;1.64.1 unsharded&lt;/th&gt;
&lt;th&gt;1.64.1 sharded&lt;/th&gt;
&lt;th&gt;1.65.1 unsharded&lt;/th&gt;
&lt;th&gt;1.65.1 sharded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--grep&lt;/code&gt; matches nothing&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;2/2 fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path filter matches nothing&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;2/2 fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--last-failed&lt;/code&gt; after a green run&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;td&gt;fails&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--only-changed&lt;/code&gt;, no test file changed&lt;/td&gt;
&lt;td&gt;passes&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;td&gt;passes&lt;/td&gt;
&lt;td&gt;0/2 fail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;code&gt;--last-failed&lt;/code&gt;, the last run file from the green run was still on disk, as it is when CI restores it from a cache or an artifact.&lt;/p&gt;

&lt;p&gt;Two results here are not in the release note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--last-failed&lt;/code&gt; after a green run still passes when sharded on 1.65.1. It ran 0 tests and exited 0 on both shards. Unsharded, the same command exits 1 with "No tests found" on both versions. The new check did not apply to this flag. Without the last run file, &lt;code&gt;--last-failed&lt;/code&gt; ran the whole suite, so this only happens when the file is restored.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--only-changed&lt;/code&gt; with no changed test file exited 0 in every cell, sharded or not, on both versions. It passed before the change, so it says nothing about the new rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The balancer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://garyparker.dev/projects/playwright-shard-balancer" rel="noopener noreferrer"&gt;playwright-shard-balancer&lt;/a&gt; plans shards through Playwright's &lt;code&gt;preprocess&lt;/code&gt; hook, so I checked whether a shard it leaves empty trips the new rule. It does not. Version 0.1.3 gave the same exit code as Playwright's split in every cell: all shards fail on a zero-test selection, and none fail when at least one test is selected. The balancer did plan differently: it left a different number of shards empty in 36 of the paired jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change in CI
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If a filter can legitimately match nothing, such as a docs-only pull request, add &lt;code&gt;--pass-with-no-tests&lt;/code&gt; to the sharded command. It turned every cell green on both versions.&lt;/li&gt;
&lt;li&gt;The flag cannot tell a docs-only pull request from a broken filter. Both select nothing and both pass. If an empty selection should never happen in your pipeline, leave the flag off: on 1.65 that mistake fails the build instead of passing on every shard.&lt;/li&gt;
&lt;li&gt;Check your sharded jobs before you upgrade. A job that was green on 1.64 with no tests to run is red on 1.65.&lt;/li&gt;
&lt;li&gt;Do not use the exit code of a sharded &lt;code&gt;--last-failed&lt;/code&gt; job as proof that tests ran. With a restored last run file from a green run, it ran nothing and exited 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Originally published at &lt;a href="https://garyparker.dev/blog/playwright-165-empty-shards" rel="noopener noreferrer"&gt;https://garyparker.dev/blog/playwright-165-empty-shards&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cicd</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
    </item>
    <item>
      <title>Hands-On with Playwright Test Agents: Planner, Generator, Healer — Measured</title>
      <dc:creator>Gary Parker</dc:creator>
      <pubDate>Thu, 12 Nov 2026 14:13:41 +0000</pubDate>
      <link>https://dev.to/garyparker/hands-on-with-playwright-test-agents-planner-generator-healer-measured-peo</link>
      <guid>https://dev.to/garyparker/hands-on-with-playwright-test-agents-planner-generator-healer-measured-peo</guid>
      <description>&lt;p&gt;Playwright ships official AI test agents — a planner that explores your app and writes a test plan, a generator that turns the plan into live-verified test code, and a healer that repairs broken tests against the running page. The vendor demos are impressive. What I couldn't find anywhere was a measured answer to the question that actually matters: &lt;strong&gt;would you keep the suite these agents produce?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I measured it. Everything below comes from &lt;a href="https://github.com/qa-gary-parker/poc-playwright-test-agents" rel="noopener noreferrer"&gt;a public PoC repo&lt;/a&gt; you can rerun yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Target: &lt;a href="https://the-internet.herokuapp.com" rel="noopener noreferrer"&gt;the-internet.herokuapp.com&lt;/a&gt;, the classic demo app. Playwright 1.62.0. Agents initialised with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright init-agents --loop=claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That generates three agent definitions plus a &lt;code&gt;playwright-test&lt;/code&gt; MCP server config — the agents run inside Claude Code (VS Code, Codex and opencode loops are also supported).&lt;/p&gt;

&lt;p&gt;The brief for both sides was identical: cover login success and failure, dynamic controls (checkbox removal, input enable), and dynamic loading (both examples). The comparison baseline is a 6-test suite written in one pass from knowledge of the app, without touching a browser — the way an engineer who knows an app writes tests. One honesty note: the baseline was authored by the orchestrating model, not a human, so treat the authoring-time comparison as indicative rather than a controlled study.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;One-pass baseline&lt;/th&gt;
&lt;th&gt;Agent-generated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scenarios&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to produce&lt;/td&gt;
&lt;td&gt;minutes&lt;/td&gt;
&lt;td&gt;planner 2m 44s + generator &lt;strong&gt;43m 22s&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of test code&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;223&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assertions per test&lt;/td&gt;
&lt;td&gt;~2.5&lt;/td&gt;
&lt;td&gt;~9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locators&lt;/td&gt;
&lt;td&gt;role/label-first, 6 CSS ids&lt;/td&gt;
&lt;td&gt;29 &lt;code&gt;getByRole&lt;/code&gt;, 7 &lt;code&gt;getByText&lt;/code&gt;, 3 CSS ids&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suite runtime&lt;/td&gt;
&lt;td&gt;10.1s&lt;/td&gt;
&lt;td&gt;8.8s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass rate&lt;/td&gt;
&lt;td&gt;6/6&lt;/td&gt;
&lt;td&gt;7/7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The healer experiment: I gave it a login test with six deliberately stale locators — wrong field ids, wrong button label, wrong flash selector, wrong heading text — simulating a suite that drifted from its app. It repaired all six in &lt;strong&gt;1m 55s&lt;/strong&gt;, correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What genuinely impressed me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live verification catches what knowledge misses.&lt;/strong&gt; The generator discovered that &lt;code&gt;/dynamic_controls&lt;/code&gt; keeps stale, hidden &lt;code&gt;#loading&lt;/code&gt; elements in the DOM — a real flake trap — and wrote around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The page keeps stale hidden #loading elements around, so target the visible one&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;loadingIndicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Wait for it...'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;visible&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="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;My one-pass baseline avoided that trap only by accident, because it happened to assert on different elements. This is the strongest argument for the agents: they test against the app as it &lt;em&gt;is&lt;/em&gt;, not as you remember it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrail judgment.&lt;/strong&gt; The plan included "attempt to fill the disabled input" — a step that would hang forever against Playwright's actionability checks. The generator recognised that and substituted &lt;code&gt;toBeDisabled()&lt;/code&gt; plus &lt;code&gt;toHaveValue('')&lt;/code&gt; assertions. Same intent, no flake. That's not template-filling; that's a decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The planner discloses scope changes.&lt;/strong&gt; Asked for two login scenarios, it produced three — and flagged the addition instead of burying it. Small thing, big trust signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the demos won't tell you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The agentic tax is real.&lt;/strong&gt; Forty-three minutes to generate seven tests, because every locator and assertion is verified in a live browser as it's written. It's unattended time — but it scales with suite size, and if you're picturing "regenerate the suite on every sprint", do that maths first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nine assertions per test cuts both ways.&lt;/strong&gt; The generated tests document the UI exhaustively: button disabled during load, loading indicator visible, message text, element gone, other button back. Thorough — and every one of those intermediate assertions is a maintenance cost when the UI legitimately changes. My baseline's ~2.5 assertions per test encode a judgment about what matters; the agent asserts everything it saw. Nobody has taught it what &lt;em&gt;not&lt;/em&gt; to check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The healer repairs — it does not refactor.&lt;/strong&gt; This is the finding I'd want every team to see before wiring a healer into CI. My stale CSS ids came back as &lt;em&gt;correct&lt;/em&gt; CSS ids, not as the semantic locators a reviewer would ask for. And it matched one button's accessible name literally — &lt;code&gt;{ name: ' Login' }&lt;/code&gt;, leading space and all. It also did something smart: it cross-referenced the known-good login test elsewhere in the repo rather than guessing. But the pattern is clear: &lt;strong&gt;healed code preserves the quality level of the code that broke.&lt;/strong&gt; Feed it a suite full of brittle selectors and it will faithfully restore your brittle selectors forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Use the planner and generator to scaffold coverage on apps you don't know well — the live verification genuinely produces better selectors than writing from memory, and the wall-clock cost is unattended. Keep the output under review, because assertion density is a liability you'll own later.&lt;/p&gt;

&lt;p&gt;Treat the healer as a colleague who proposes fixes in a PR, not as a silent auto-repair step. It's fast, accurate, and completely uncritical of what it restores — which makes it exactly as good as the suite you already have.&lt;/p&gt;

&lt;p&gt;If you're thinking about what your test suite means in a world where agents both write and consume it, that's the thread running through &lt;a href="https://garyparker.dev/playbook" rel="noopener noreferrer"&gt;the playbook&lt;/a&gt; — start with &lt;a href="https://garyparker.dev/blog/the-cost-of-a-slow-test-suite" rel="noopener noreferrer"&gt;the cost of a slow test suite&lt;/a&gt;, because agent workflows make suite speed matter more, not less.&lt;/p&gt;

&lt;p&gt;Everything here is reproducible: &lt;a href="https://github.com/qa-gary-parker/poc-playwright-test-agents" rel="noopener noreferrer"&gt;qa-gary-parker/poc-playwright-test-agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://garyparker.dev/blog/hands-on-with-playwright-test-agents" rel="noopener noreferrer"&gt;https://garyparker.dev/blog/hands-on-with-playwright-test-agents&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
