<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged webapi</title>
<link>https://coderlegion.com/tag/webapi</link>
<description>Powered by Question2Answer</description>
<item>
<title>Build Direct-to-Disk Web Editors with the New useFileSystem Hook</title>
<link>https://coderlegion.com/25266/build-direct-to-disk-web-editors-with-the-new-usefilesystem-hook</link>
<description>&lt;p&gt;We have all built or used web-based text and configuration tools. The absolute worst part of the user experience is the continuous download cycle. Want to save your progress? Click download. Need to make a quick adjustment? Download &lt;code&gt;config (3).json&lt;/code&gt; again. &lt;/p&gt;
&lt;p&gt;Modern browsers solve this with the &lt;strong&gt;File System Access API&lt;/strong&gt;, which lets web applications read and write directly to files on the user's local disk. However, managing file handles, tracking reading/saving states, and handling user cancellations in React can introduce massive amounts of boilerplate.&lt;/p&gt;
&lt;p&gt;In the latest release of &lt;code&gt;react-hook-lab&lt;/code&gt;, we are excited to introduce the &lt;code&gt;useFileSystem&lt;/code&gt; hook to handle all of this for you.&lt;/p&gt;
&lt;h4 id=&quot;what-is-usefilesystem-&quot;&gt;What is useFileSystem?&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useFileSystem&lt;/code&gt; is a declarative, type-safe React hook that wraps the browser's native File System Access API. It allows you to prompt users to open a file, read its content directly into your state, and continuously write changes back to that same file on disk without prompting for re-downloads.&lt;/p&gt;
&lt;p&gt;Here are some of its primary features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-time Status Tracking:&lt;/strong&gt; Easily handle UI states using the built-in states: &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;picking&lt;/code&gt;, &lt;code&gt;reading&lt;/code&gt;, &lt;code&gt;saving&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardized State Management:&lt;/strong&gt; Keeps track of the active &lt;code&gt;File&lt;/code&gt; metadata, raw &lt;code&gt;content&lt;/code&gt;, and the underlying &lt;code&gt;FileSystemFileHandle&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart Error Handling:&lt;/strong&gt; Gracefully handles user cancellations (like closing the file picker) without throwing unhandled exceptions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's dive into some practical code examples to see it in action.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;example-1-creating-a-basic-file-reader-amp-writer&quot;&gt;Example 1: Creating a Basic File Reader &amp;amp; Writer&lt;/h4&gt;
&lt;p&gt;This simple component allows a user to open any text file, view its raw contents, and append a timestamped log line directly back to that same file on disk.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import React from &quot;react&quot;;
import { useFileSystem } from &quot;react-hook-lab&quot;;

export function SimpleLogWriter() {
  const {
    open,
    save,
    content,
    status,
    file,
    isSupported
  } = useFileSystem({
    accept: { &quot;text/plain&quot;: [&quot;.txt&quot;] },
    description: &quot;Log Files&quot;
  });

  if (!isSupported) {
    return &amp;lt;p&amp;gt;Your browser does not support direct file system access.&amp;lt;/p&amp;gt;;
  }

  const handleAppendLog = () =&amp;gt; {
    const existingContent = content || &quot;&quot;;
    const newLog = `${existingContent}\n[LOG] Updated at ${new Date().toLocaleTimeString()}`;
    save(newLog);
  };

  return (
    &amp;lt;div style={{ padding: &quot;1rem&quot;, border: &quot;1px solid #ccc&quot;, borderRadius: &quot;8px&quot; }}&amp;gt;
      &amp;lt;h3&amp;gt;Direct-to-Disk Log Writer&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;Status: &amp;lt;strong&amp;gt;{status}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
      {file &amp;amp;&amp;amp; &amp;lt;p&amp;gt;Editing file: &amp;lt;code&amp;gt;{file.name}&amp;lt;/code&amp;gt; ({file.size} bytes)&amp;lt;/p&amp;gt;}

      &amp;lt;div style={{ display: &quot;flex&quot;, gap: &quot;0.5rem&quot;, marginBottom: &quot;1rem&quot; }}&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; open()}&amp;gt;Open Log File&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={handleAppendLog} disabled={!file || status === &quot;saving&quot;}&amp;gt;
          Append Log Entry
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;pre style={{ background: &quot;#f5f5f5&quot;, padding: &quot;1rem&quot;, borderRadius: &quot;4px&quot; }}&amp;gt;
        {content || &quot;No file loaded. Click 'Open Log File' to load a text document.&quot;}
      &amp;lt;/pre&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;example-2-markdown-editor-with-save-as-support&quot;&gt;Example 2: Markdown Editor with Save As Support&lt;/h4&gt;
&lt;p&gt;In a real-world scenario, you want to allow users to edit text in a text area, write changes to the original file, and trigger a &quot;Save As&quot; prompt when they want to create a new copy or rename it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import React, { useState, useEffect } from &quot;react&quot;;
import { useFileSystem } from &quot;react-hook-lab&quot;;

export function MarkdownEditor() {
  const {
    open,
    save,
    saveAs,
    content,
    file,
    reset,
    status
  } = useFileSystem({
    accept: { &quot;text/markdown&quot;: [&quot;.md&quot;] },
    description: &quot;Markdown Documents&quot;
  });

  const [editorText, setEditorText] = useState(&quot;&quot;);

  // Keep local editor state in sync when a file is loaded
  useEffect(() =&amp;gt; {
    if (content !== null) {
      setEditorText(content);
    }
  }, [content]);

  const handleNewFile = () =&amp;gt; {
    reset();
    setEditorText(&quot;&quot;);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{file ? `Editing: ${file.name}` : &quot;New Document *&quot;}&amp;lt;/h2&amp;gt;
      &amp;lt;textarea
        value={editorText}
        onChange={(e) =&amp;gt; setEditorText(e.target.value)}
        rows={10}
        style={{ width: &quot;100%&quot;, fontFamily: &quot;monospace&quot;, padding: &quot;0.5rem&quot; }}
        placeholder=&quot;Type your markdown here...&quot;
      /&amp;gt;
      &amp;lt;div style={{ marginTop: &quot;1rem&quot;, display: &quot;flex&quot;, gap: &quot;0.5rem&quot; }}&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; open()}&amp;gt;Open File&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; save(editorText)} disabled={status === &quot;saving&quot;}&amp;gt;
          Save
        &amp;lt;/button&amp;gt;
        &amp;lt;button
          onClick={() =&amp;gt; saveAs(editorText, { suggestedName: &quot;draft.md&quot; })}
          disabled={status === &quot;saving&quot;}
        &amp;gt;
          Save As...
        &amp;lt;/button&amp;gt;
        &amp;lt;button onClick={handleNewFile}&amp;gt;New File (Reset)&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;p style={{ fontSize: &quot;0.85rem&quot;, color: &quot;#666&quot; }}&amp;gt;Current status: {status}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h4&gt;
&lt;p&gt;Web apps are becoming more powerful, blurring the lines between standard websites and full-fledged desktop applications. Direct-to-disk interaction using the &lt;code&gt;useFileSystem&lt;/code&gt; hook is an incredible tool for developer portals, configuration managers, web-based editors, and local-first utilities.&lt;/p&gt;
&lt;p&gt;Give it a try in your next project!&lt;/p&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NPM Package:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;https://www.npmjs.com/package/react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;https://github.com/Saurav-TB-Pandey/react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn Profile:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;https://www.linkedin.com/in/pandeysaurav/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/elevating-browser-ux-say-goodbye-to.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/build-direct-to-disk-web-editors-with-the-new-usefilesystem-hook-3k6e&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25266/build-direct-to-disk-web-editors-with-the-new-usefilesystem-hook</guid>
<pubDate>Wed, 26 Aug 2026 11:53:50 +0000</pubDate>
</item>
<item>
<title>Graceful Test Cancellation: Why Your Playwright Tests Need AbortSignal</title>
<link>https://coderlegion.com/11781/graceful-test-cancellation-why-your-playwright-tests-need-abortsignal</link>
<description>&lt;p&gt;There is a class of bugs that only shows up at scale. Not in your code. Not in your infrastructure. In the gap between them -- where your test suite meets your staging environment.&lt;/p&gt;
&lt;p&gt;I am talking about resource leaks from timed-out tests.&lt;/p&gt;
&lt;h3 id=&quot;the-scenario-you-have-definitely-seen&quot;&gt;The Scenario You Have Definitely Seen&lt;/h3&gt;
&lt;p&gt;A developer pushes a change. CI kicks off 300 Playwright tests across 8 workers. Fifteen of those tests hit a slow endpoint -- maybe the service is under load, maybe there is a network hiccup. They time out after 30 seconds each.&lt;/p&gt;
&lt;p&gt;Playwright marks them as failed and moves on. Good. That is what it should do.&lt;/p&gt;
&lt;p&gt;But here is what does not happen: the HTTP requests those tests initiated do not get cancelled. They are still in flight. The server is still processing them. The TCP connections are still open.&lt;/p&gt;
&lt;p&gt;Fifteen tests, each making two or three API calls. That is 30 to 45 orphaned requests still hammering your staging environment. They will finish eventually -- or time out on their own after another 30 seconds. In the meantime, they consume server threads, database connections, and memory.&lt;/p&gt;
&lt;p&gt;Now imagine this happens every CI run. Multiple times a day. Across multiple teams sharing the same staging environment.&lt;/p&gt;
&lt;p&gt;This is not hypothetical. This is Tuesday.&lt;/p&gt;
&lt;h3 id=&quot;why-playwright-does-not-handle-this&quot;&gt;Why Playwright Does Not Handle This&lt;/h3&gt;
&lt;p&gt;This is not a Playwright bug. It is a fundamental constraint of how JavaScript async operations work.&lt;/p&gt;
&lt;p&gt;When Playwright times out a test, it stops waiting for the test function to resolve. But &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;axios&lt;/code&gt;, WebSocket connections, and other async operations do not know the test has ended. They were started, they are running, and nothing has told them to stop.&lt;/p&gt;
&lt;p&gt;JavaScript has no built-in mechanism to kill arbitrary async operations from outside. You cannot reach into a running &lt;code&gt;fetch&lt;/code&gt; and terminate it. You cannot walk the promise chain and cancel everything.&lt;/p&gt;
&lt;p&gt;What you can do is provide a cancellation token upfront -- a signal that says &quot;when I tell you to stop, stop.&quot; This is what &lt;code&gt;AbortController&lt;/code&gt; and &lt;code&gt;AbortSignal&lt;/code&gt; are for.&lt;/p&gt;
&lt;h3 id=&quot;abortsignal-a-quick-refresher&quot;&gt;AbortSignal: A Quick Refresher&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AbortController&lt;/code&gt; is part of the web platform and is also available in Node.js. It provides a simple cancellation mechanism:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const controller = new AbortController();

// Start an operation with the signal
fetch('/api/data', { signal: controller.signal });

// Cancel it from outside
controller.abort();
// The fetch promise rejects with an AbortError
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight is that you decide at the start of the operation that it should be cancellable. You pass the signal in. Later, when you need to cancel, you call &lt;code&gt;abort()&lt;/code&gt; on the controller.&lt;/p&gt;
&lt;p&gt;This is the missing piece for Playwright tests.&lt;/p&gt;
&lt;h3 id=&quot;wiring-abortsignal-into-the-test-lifecycle&quot;&gt;Wiring AbortSignal Into the Test Lifecycle&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;@playwright-labs/fixture-abort&lt;/code&gt; package provides this wiring. It extends Playwright's &lt;code&gt;test&lt;/code&gt; with fixtures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;abortController&lt;/code&gt; -- a fresh &lt;code&gt;AbortController&lt;/code&gt; for the current test&lt;/li&gt;
&lt;li&gt;&lt;code&gt;signal&lt;/code&gt; -- the associated &lt;code&gt;AbortSignal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useAbortController(options?)&lt;/code&gt; -- returns the controller with optional abort callback&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useSignalWithTimeout(ms)&lt;/code&gt; -- returns a signal that auto-aborts after a duration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The controller is automatically aborted when the test times out. Every operation that received the signal cancels immediately.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should fetch data with cancellation support', async ({ signal }) =&amp;gt; {
  const response = await fetch('https://api.example.com/data', {
    signal
  });
  const data = await response.json();
  expect(data).toBeDefined();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this test times out, &lt;code&gt;signal&lt;/code&gt; fires. The fetch call is cancelled. The server stops processing. The connection is freed. No cleanup code required.&lt;/p&gt;
&lt;h3 id=&quot;where-this-pattern-really-shines&quot;&gt;Where This Pattern Really Shines&lt;/h3&gt;
&lt;h4 id=&quot;polling-loops&quot;&gt;Polling Loops&lt;/h4&gt;
&lt;p&gt;Tests that poll an endpoint are the most common source of zombie requests. Without an abort signal, a polling loop can run indefinitely after a timeout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should handle long polling', async ({ signal }) =&amp;gt; {
  while (!signal.aborted) {
    const response = await fetch('/api/poll', {
      signal
    });

    if (response.status === 200) {
      const data = await response.json();
      expect(data.ready).toBe(true);
      return;
    }

    await new Promise(resolve =&amp;gt; setTimeout(resolve, 1000));
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two layers of protection here. The &lt;code&gt;while (!signal.aborted)&lt;/code&gt; check prevents the loop from continuing after cancellation. The &lt;code&gt;signal&lt;/code&gt; on the fetch call cancels any in-flight request. If the test times out mid-request, the fetch is cancelled. If it times out between requests, the loop condition catches it.&lt;/p&gt;
&lt;h4 id=&quot;parallel-request-batches&quot;&gt;Parallel Request Batches&lt;/h4&gt;
&lt;p&gt;When tests fire multiple requests simultaneously, all of them need to be cancellable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should aggregate data from multiple services', async ({ signal }) =&amp;gt; {
  const [users, products, analytics] = await Promise.all([
    fetch('/api/users', { signal }),
    fetch('/api/products', { signal }),
    fetch('/api/analytics', { signal })
  ]);

  // Process results...
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One signal, shared across all requests. When it fires, everything stops.&lt;/p&gt;
&lt;h4 id=&quot;retry-logic&quot;&gt;Retry Logic&lt;/h4&gt;
&lt;p&gt;Tests with retry mechanisms are particularly dangerous without abort signals. A retry loop can keep hammering a failing endpoint long after the test has timed out:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should retry on failure', async ({ signal }) =&amp;gt; {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt &amp;lt; 5 &amp;amp;&amp;amp; !signal.aborted; attempt++) {
    try {
      const response = await fetch('/api/flaky-endpoint', {
        signal
      });
      if (response.ok) {
        const data = await response.json();
        expect(data).toBeDefined();
        return;
      }
    } catch (err) {
      lastError = err as Error;
      if (signal.aborted) break;
      await new Promise(resolve =&amp;gt; setTimeout(resolve, 1000 * (attempt + 1)));
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The signal check in the loop condition, the signal on the fetch call, and the signal check in the catch block create a robust cancellation pattern that exits quickly no matter where in the retry cycle the timeout occurs.&lt;/p&gt;
&lt;h3 id=&quot;timeout-signals&quot;&gt;Timeout Signals&lt;/h3&gt;
&lt;p&gt;Need a signal that auto-aborts after a specific duration, independent of the test timeout? Use &lt;code&gt;useSignalWithTimeout&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should complete API call within 5 seconds', async ({ useSignalWithTimeout }) =&amp;gt; {
  const timeoutSignal = useSignalWithTimeout(5000);

  const response = await fetch('https://api.example.com/slow', {
    signal: timeoutSignal
  });
  const data = await response.json();
  expect(data).toBeDefined();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;custom-expect-matchers&quot;&gt;Custom Expect Matchers&lt;/h3&gt;
&lt;p&gt;The package includes matchers for asserting abort states:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { test, expect } from '@playwright-labs/fixture-abort';

test('should verify signal states', async ({ signal, abortController }) =&amp;gt; {
  expect(signal).toBeActive();
  expect(abortController).toHaveActiveSignal();

  abortController.abort('done');

  expect(signal).toBeAborted();
  expect(signal).toBeAbortedWithReason('done');
  expect(abortController).toHaveAbortedSignal();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-bigger-picture-tests-as-good-citizens&quot;&gt;The Bigger Picture: Tests as Good Citizens&lt;/h3&gt;
&lt;p&gt;There is a broader principle here. Your tests are not running in isolation. They share infrastructure with other tests, other teams, and often with real users (if staging is a shared environment).&lt;/p&gt;
&lt;p&gt;A test that leaks resources is like a function that leaks memory. It might not matter once. It matters a lot when it happens thousands of times a day.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AbortSignal&lt;/code&gt; integration is not just about preventing flaky tests. It is about making your test suite a responsible consumer of shared resources. It is about ensuring that when a test ends -- whether it passes, fails, or times out -- it cleans up after itself completely.&lt;/p&gt;
&lt;h3 id=&quot;the-cost-of-not-doing-this&quot;&gt;The Cost of Not Doing This&lt;/h3&gt;
&lt;p&gt;Without abort signal integration, you are relying on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Server-side timeouts&lt;/strong&gt; to eventually clean up orphaned requests (adding load until they fire).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection pool limits&lt;/strong&gt; to cap the damage (causing legitimate requests to queue).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual investigation&lt;/strong&gt; when staging environments degrade (burning engineering time).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these are solutions. They are damage control.&lt;/p&gt;
&lt;h3 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @playwright-labs/fixture-abort
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package provides raw Playwright fixtures. Import &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;expect&lt;/code&gt; from &lt;code&gt;@playwright-labs/fixture-abort&lt;/code&gt; instead of &lt;code&gt;@playwright/test&lt;/code&gt;, use &lt;code&gt;signal&lt;/code&gt; in your async operations, and your tests clean up after themselves automatically.&lt;/p&gt;
&lt;p&gt;Full source code and documentation: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vitalics/playwright-labs&quot;&gt;github.com/vitalics/playwright-labs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The change is small. The impact on your infrastructure reliability is not.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11781/graceful-test-cancellation-why-your-playwright-tests-need-abortsignal</guid>
<pubDate>Mon, 16 Feb 2026 19:41:08 +0000</pubDate>
</item>
</channel>
</rss>