<?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: Jagadeesh Jayachandran</title>
    <description>The latest articles on Forem by Jagadeesh Jayachandran (@jagadeeshjayachandran).</description>
    <link>https://forem.com/jagadeeshjayachandran</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%2F1109876%2Fc30183f3-0bdf-4727-bfed-c29fb73bef6b.jpeg</url>
      <title>Forem: Jagadeesh Jayachandran</title>
      <link>https://forem.com/jagadeeshjayachandran</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jagadeeshjayachandran"/>
    <language>en</language>
    <item>
      <title>I Built a Chrome Extension That Finally Makes Bookmarks Searchable — Here's How</title>
      <dc:creator>Jagadeesh Jayachandran</dc:creator>
      <pubDate>Fri, 06 Mar 2026 17:01:46 +0000</pubDate>
      <link>https://forem.com/jagadeeshjayachandran/i-built-a-chrome-extension-that-finally-makes-bookmarks-searchable-heres-how-4a4d</link>
      <guid>https://forem.com/jagadeeshjayachandran/i-built-a-chrome-extension-that-finally-makes-bookmarks-searchable-heres-how-4a4d</guid>
      <description>&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;You know that feeling. You saved a link weeks ago — a brilliant tutorial, a useful tool, a must-read article. Now you need it. You type something vague into your bookmarks search bar... and get nothing.&lt;/p&gt;

&lt;p&gt;So you open your bookmarks manager and start clicking through folders named "Useful", "Dev Stuff", "Read Later", and "Misc". Twenty minutes later, you give up and just Google it again.&lt;/p&gt;

&lt;p&gt;I had this problem every single day. And after years of dealing with it, I decided to build a proper solution.&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;Recall: Smart Bookmark Search&lt;/strong&gt; was born.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Recall?
&lt;/h2&gt;

&lt;p&gt;Recall is a Chrome extension that transforms your bookmark library from a cluttered folder system into an intelligent, searchable knowledge base. Instead of remembering exact titles or folder paths, you can search like a human:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"that webpack config article"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"React tutorial from last week"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"design tools from the CSS conference"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it actually finds them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome Web Store:&lt;/strong&gt; &lt;a href="https://chromewebstore.google.com/detail/recall-smart-bookmark-sea/iijijlphflobabngdpakfefafhgnnflc" rel="noopener noreferrer"&gt;Install Recall&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Foundation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Content Indexing — Going Beyond the Title
&lt;/h3&gt;

&lt;p&gt;The biggest insight was this: &lt;strong&gt;the page title is almost never what you remember&lt;/strong&gt;. You remember &lt;em&gt;what the page was about&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So when you save a bookmark, Recall injects a content extractor script that scrapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page title and URL&lt;/li&gt;
&lt;li&gt;Meta description and keywords&lt;/li&gt;
&lt;li&gt;H1, H2, H3 headings&lt;/li&gt;
&lt;li&gt;Main body paragraphs&lt;/li&gt;
&lt;li&gt;Image alt text&lt;/li&gt;
&lt;li&gt;Link titles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It then extracts the &lt;strong&gt;top 50 keywords by frequency&lt;/strong&gt;, weighted by their position in the document. This becomes your searchable index.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified keyword extraction&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractKeywords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&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;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&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;frequency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &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;word&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isStopWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;word&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;h3&gt;
  
  
  2. Fuzzy Search with Levenshtein Distance
&lt;/h3&gt;

&lt;p&gt;Exact string matching is useless when you can't remember the exact title. Real search needs to tolerate typos and partial matches.&lt;/p&gt;

&lt;p&gt;I implemented fuzzy matching using the &lt;strong&gt;Levenshtein distance algorithm&lt;/strong&gt; — so "devinci" still matches "DaVinci", and "recat" still finds "React".&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Smart Relevance Scoring
&lt;/h3&gt;

&lt;p&gt;Not all matches are equal. Recall scores results across multiple signals:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tag match&lt;/td&gt;
&lt;td&gt;+25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phrase in title&lt;/td&gt;
&lt;td&gt;+20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Title keyword match&lt;/td&gt;
&lt;td&gt;+20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description match&lt;/td&gt;
&lt;td&gt;+12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL keyword&lt;/td&gt;
&lt;td&gt;+15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content keyword&lt;/td&gt;
&lt;td&gt;+10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saved recently (&amp;lt;7 days)&lt;/td&gt;
&lt;td&gt;+5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This means if you tagged something "css-tricks", it bubbles to the top immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Auto-Categorization
&lt;/h3&gt;

&lt;p&gt;Nobody wants to manually categorize every bookmark. Recall uses URL pattern matching and content keyword analysis to auto-assign one of 12 categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development, Education, Entertainment, Productivity, Finance, Health, Design, Food, Travel, News, Books, Other&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example category detection&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stackoverflow.com&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Development&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coursera.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;udemy.com&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Education&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;
  
  
  Features That Make It Practical
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Visual Snapshots
&lt;/h3&gt;

&lt;p&gt;When you save a bookmark, Recall automatically captures a screenshot of the page. This is surprisingly useful — you often recognize a page visually before you remember its title.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading List
&lt;/h3&gt;

&lt;p&gt;Shift+Click the save button to add anything to your reading queue. Your dashboard shows the top 3 items at all times, so nothing gets buried.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate Detection
&lt;/h3&gt;

&lt;p&gt;Uses URL normalization (strips UTM params, tracking tokens) plus &lt;strong&gt;Jaccard similarity on titles&lt;/strong&gt; with an 80% threshold. It catches duplicates even when URLs are slightly different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dead Link Validation
&lt;/h3&gt;

&lt;p&gt;Batch HTTP status checking with Archive.org fallback for 404s. Dead links get marked with a warning badge so you can clean up your library.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges I Solved
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Storage limits:&lt;/strong&gt; Chrome extensions have a 10MB default storage quota. For large bookmark collections with screenshots, this fills up fast. I switched to &lt;code&gt;chrome.storage.local&lt;/code&gt; with &lt;code&gt;unlimitedStorage&lt;/code&gt; permission and added base64 compression for snapshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manifest V3 restrictions:&lt;/strong&gt; Background service workers in MV3 are ephemeral — they spin down when idle. I had to carefully architect the indexing pipeline to handle interrupted jobs gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search speed:&lt;/strong&gt; With 1,000+ bookmarks, naive search was too slow. Pre-building keyword indexes at save time (rather than search time) brought results down to under 300ms.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic search&lt;/strong&gt; using local embeddings (no server required)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firefox support&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync across devices&lt;/strong&gt; (opt-in, encrypted)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser history integration&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;If you're drowning in bookmarks, give Recall a try. It's free, requires no account, and keeps everything 100% on your device.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://chromewebstore.google.com/detail/recall-smart-bookmark-sea/iijijlphflobabngdpakfefafhgnnflc" rel="noopener noreferrer"&gt;Install on Chrome Web Store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love to hear what features you'd want next — drop them in the comments below!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Writing Brittle Tests: The Smart Locator Strategy Every Playwright Dev Needs</title>
      <dc:creator>Jagadeesh Jayachandran</dc:creator>
      <pubDate>Wed, 18 Feb 2026 22:18:47 +0000</pubDate>
      <link>https://forem.com/jagadeeshjayachandran/stop-writing-brittle-tests-the-smart-locator-strategy-every-playwright-dev-needs-45ki</link>
      <guid>https://forem.com/jagadeeshjayachandran/stop-writing-brittle-tests-the-smart-locator-strategy-every-playwright-dev-needs-45ki</guid>
      <description>&lt;p&gt;I spent hours debugging a flaky test that broke because of one line of XPath.&lt;/p&gt;

&lt;p&gt;The fix? A single Playwright locator that took 10 seconds to write.&lt;/p&gt;

&lt;p&gt;Here's what I wish I knew earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Truth
&lt;/h2&gt;

&lt;p&gt;If your tests are flaky, your locators are the problem.&lt;/p&gt;

&lt;p&gt;Traditional XPath and CSS selectors navigate the DOM like a tree. The moment a developer reorders elements or changes structure — your tests break. Every. Single. Time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Brittle - breaks if DOM structure changes&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;div#main &amp;gt; ul:nth-child(2) &amp;gt; li:first-child &amp;gt; button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Human Vision vs Robot Vision
&lt;/h2&gt;

&lt;p&gt;Playwright takes a completely different approach. Instead of hunting for IDs and class names (robot vision), it targets elements the way a real user would — by what they &lt;em&gt;see&lt;/em&gt; on screen (human vision).&lt;/p&gt;

&lt;p&gt;The gold standard? &lt;code&gt;page.getByRole()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It taps into the &lt;strong&gt;accessibility tree&lt;/strong&gt; — the same structure screen readers use. If a screen reader can find it, Playwright can test it. And it survives refactors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Smart - survives DOM changes&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;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Locator Priority Hierarchy
&lt;/h2&gt;

&lt;p&gt;Follow this order and you'll solve 80% of your locator problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. getByRole&lt;/strong&gt; — Always start here. Works for buttons, headings, checkboxes, links, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. getByText / getByLabel&lt;/strong&gt; — Fall back to visible on-screen text or form labels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. getByTestId&lt;/strong&gt; — Ask your dev to add a &lt;code&gt;data-testid&lt;/code&gt; attribute. It's stable and won't change by accident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. CSS/XPath&lt;/strong&gt; — Last resort only. Even then, paste it into an LLM — it'll convert it to a &lt;code&gt;getByRole&lt;/code&gt; locator for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: No More sleep() Hacks
&lt;/h2&gt;

&lt;p&gt;Playwright's built-in auto-wait means every locator automatically waits for the element to be attached, visible, stable, and enabled before acting. No more arbitrary timeouts.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
js
// You no longer need this
await page.waitForTimeout(3000)

In this code snippet, the playwright library is being used to automatically handle clicking on a button element with the role of 'button' and the name 'Submit'. The code is locating the button using the getByRole query function and then triggering a click event on it using the click() method. This allows for automated interaction with the webpage in order to submit a form or perform some other action.y {
  const submitButton = await page.getByRole('button', { name: 'Submit' });
  await submitButton.click();
} catch(err) {
  console.error("Error clicking submit button: ", err);
}
This is Part 2 of my Playwright 80/20 Pareto Principle series. Part 3 covers Playwright Actions — coming soon!

🎬 Watch the full video here: https://www.youtube.com/watch?v=6IaCQ70cNVc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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