<?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: Fonzi Vazquez</title>
    <description>The latest articles on Forem by Fonzi Vazquez (@fonzi).</description>
    <link>https://forem.com/fonzi</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%2F810129%2Ff9376d30-5c11-4496-8d16-f25387a5db3d.png</url>
      <title>Forem: Fonzi Vazquez</title>
      <link>https://forem.com/fonzi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fonzi"/>
    <language>en</language>
    <item>
      <title>HTML5 Canvas Testing with CanvasGrid and Playwright 🎭</title>
      <dc:creator>Fonzi Vazquez</dc:creator>
      <pubDate>Sat, 04 Oct 2025 03:46:27 +0000</pubDate>
      <link>https://forem.com/fonzi/testing-html5-canvas-with-canvasgrid-and-playwright-5h4c</link>
      <guid>https://forem.com/fonzi/testing-html5-canvas-with-canvasgrid-and-playwright-5h4c</guid>
      <description>&lt;h2&gt;
  
  
  Why Canvas Testing Is Hard
&lt;/h2&gt;

&lt;p&gt;HTML5 canvas elements are everywhere games, maps, drawing apps, custom visualizations. But testing them has always been a pain. Unlike regular DOM elements, you can't just select, click, or inspect canvas content with standard browser automation tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Camps of Canvas Testing
&lt;/h2&gt;

&lt;p&gt;When it comes to automating canvas testing, there are generally two camps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deserialization and Direct Calls:&lt;/strong&gt; Some tools try to "read" the canvas by deserializing its pixel data or using direct API calls. This can work for simple cases, but quickly gets complicated for dynamic or interactive canvases, and often requires deep integration with the app's code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ML/AI Training:&lt;/strong&gt; Others use machine learning or AI to visually recognize elements and simulate clicks or drags. This approach is powerful, but requires training data, setup, and can be overkill for simple interactions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted something easier to plug in for basic canvas actions like clicking, dragging, or finding tooltips. So I thought: why not just overlay a dynamic grid at test runtime? That way, you can interact with any region of the canvas, without needing to reverse-engineer its internals or train a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation: MapGrab Broke, So I Built This
&lt;/h2&gt;

&lt;p&gt;I started working on &lt;code&gt;canvas-grid&lt;/code&gt; after my MapGrab integration broke due to some layering confusion. Tooltips and overlays were appearing in the DOM only after canvas interactions, and existing tools couldn't reliably automate or verify these behaviors. I needed a way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click and drag on specific canvas regions&lt;/li&gt;
&lt;li&gt;Detect tooltips or overlays that appear in the DOM after canvas events&lt;/li&gt;
&lt;li&gt;Sample colors and verify rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introducing CanvasGrid
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;canvas-grid&lt;/code&gt; is a Playwright-first library for testing HTML5 canvas elements using a grid-based approach. It's experimental, but it makes previously impossible tests easy-ish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overlay a grid on any canvas&lt;/li&gt;
&lt;li&gt;Click, drag, and hover on grid cells&lt;/li&gt;
&lt;li&gt;Sample pixel colors&lt;/li&gt;
&lt;li&gt;Find tooltips or DOM elements triggered by canvas events&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanvasGrid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas-grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas testing example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-canvas-app.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CanvasGrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gridSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 10 cols, 8 rows&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clickCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hoverCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sampleCell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Now check for tooltips or overlays&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Expected text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It looks when it injects a grid into a canvas.
&lt;/h2&gt;

&lt;p&gt;These cnavas injections happen dynamically on test run time, so no need to update the server side. &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%2Fe15i4rie9wuxae9vdqtl.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%2Fe15i4rie9wuxae9vdqtl.png" alt="To see the video go to my blog linked below!" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Benefits
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;canvas-grid&lt;/code&gt;, you can finally automate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag-and-drop interactions&lt;/li&gt;
&lt;li&gt;Pixel-perfect UI checks&lt;/li&gt;
&lt;li&gt;DOM overlays triggered by canvas events&lt;/li&gt;
&lt;li&gt;Regression tests for games, maps, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The project is still experimental, but you can find it in my repo. If you have feedback or want to contribute, reach out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Find CanvasGrid
&lt;/h2&gt;

&lt;p&gt;CanvasGrid lives here for now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fonzi.xyz/blog" rel="noopener noreferrer"&gt;Personal Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Bison-Software/CanvasGrid" rel="noopener noreferrer"&gt;GitHub: Bison-Software/CanvasGrid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@bisonsoftware/canvas-grid-playwright?activeTab=readme" rel="noopener noreferrer"&gt;npm: @bisonsoftware/canvas-grid-playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project is under my new company, Bison Software LLC.&lt;/p&gt;

&lt;p&gt;Learn more at &lt;a href="https://bison.software" rel="noopener noreferrer"&gt;bison.software&lt;/a&gt;&lt;/p&gt;

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