<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged test</title>
<link>https://coderlegion.com/tag/test</link>
<description>Powered by Question2Answer</description>
<item>
<title>Top Staging &amp; Sandbox Tools to Test Site Updates Before Going Live</title>
<link>https://coderlegion.com/32892/top-staging-sandbox-tools-to-test-site-updates-before-going-live</link>
<description>&lt;p&gt;Updating a website without testing the changes first can put an entire business at risk. A new plugin can break a WordPress installation. A theme update can damage a page layout. A change to a database can affect customer records, while an application deployment can introduce errors that prevent users from completing purchases or submitting forms.&lt;/p&gt;
&lt;p&gt;Staging environments and sandbox tools help website owners identify these problems before changes reach the live site. They provide separate environments where developers can install updates, test code, review designs, and check functionality without directly modifying the production website.&lt;/p&gt;
&lt;p&gt;For businesses that depend on their websites for sales, customer communication, publishing, or daily operations, testing before deployment is an important part of website maintenance.&lt;/p&gt;
&lt;p&gt;Several platforms offer different approaches to this process. Managed WordPress hosts provide staging environments within their hosting dashboards. Web development platforms create temporary previews for new code. Local development tools allow developers to build and test websites on their computers.&lt;/p&gt;
&lt;p&gt;The best option depends on the website's technology, hosting arrangement, development process, and security requirements.&lt;/p&gt;
&lt;p&gt;What Are Staging and Sandbox Tools?&lt;br&gt;
A staging environment is a separate copy of a website or application used to test changes before deployment. Depending on the platform, it may include copies of the website's files, database, configuration, and other components required to reproduce the production environment.&lt;/p&gt;
&lt;p&gt;A sandbox is an isolated environment where developers can experiment with code, software configurations, integrations, or other changes. Some sandbox tools focus on individual components, while others support an entire website or application.&lt;/p&gt;
&lt;p&gt;These terms overlap, but they are not always interchangeable. A staging environment usually aims to reproduce the live website closely. A local development environment supports building and testing on a developer's computer. A preview deployment publishes a version of an application to a temporary URL so developers, clients, or other team members can review it.&lt;/p&gt;
&lt;p&gt;The main purpose is the same: identify problems before releasing changes to users.&lt;/p&gt;
&lt;p&gt;A suitable testing environment can help teams evaluate plugin compatibility, check page layouts, test forms, inspect application behavior, and review changes before deployment. However, a staging site does not automatically reproduce every production condition. Differences in server configuration, traffic, external services, payment integrations, and database contents can affect test results.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Kinsta Staging Environments&lt;br&gt;
Kinsta provides managed WordPress hosting with staging environments that allow website owners to test changes separately from their production websites.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Its standard staging environment is available for WordPress installations, although custom plans may limit the number of environments available. Kinsta also offers premium staging options for customers who require additional environments or resources for more demanding development and testing tasks.&lt;/p&gt;
&lt;p&gt;The platform is useful for website owners who want to test WordPress updates without building a separate testing infrastructure from scratch.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Separate staging environments for WordPress installations.&lt;/p&gt;
&lt;p&gt;Testing for WordPress core updates, themes, plugins, and custom code.&lt;/p&gt;
&lt;p&gt;Options for moving changes between staging and production.&lt;/p&gt;
&lt;p&gt;Selective deployment options for files, database content, and specific database tables.&lt;/p&gt;
&lt;p&gt;A managed hosting dashboard for handling environments.&lt;/p&gt;
&lt;p&gt;Kinsta's selective deployment options can be particularly useful when a developer needs to transfer specific changes rather than replace every component of the live website.&lt;/p&gt;
&lt;p&gt;However, database changes require careful review. A database push can overwrite newer production information, depending on what is transferred. Sites that receive orders, registrations, comments, or other user submissions need a deployment procedure that protects data created after the staging copy was made.&lt;/p&gt;
&lt;p&gt;Best for: WordPress businesses, publishers, agencies, and developers who want managed staging alongside their hosting service.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;WP Engine Staging and Development Environments&lt;br&gt;
WP Engine provides separate WordPress environments for production, staging, and development. Each environment operates independently, allowing developers to test updates without directly affecting the live installation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The platform also offers sandbox sites designed for WordPress development. These provide a development environment for work that does not need to run on the production website.&lt;/p&gt;
&lt;p&gt;This arrangement is useful for teams that maintain business websites, customer projects, or multiple WordPress installations and need a clear separation between development work and live operations.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Separate production, staging, and development environments.&lt;/p&gt;
&lt;p&gt;Independent WordPress installations for testing.&lt;/p&gt;
&lt;p&gt;Sandbox sites for development work.&lt;/p&gt;
&lt;p&gt;Support for testing plugin and theme changes.&lt;/p&gt;
&lt;p&gt;An environment management workflow within the hosting platform.&lt;/p&gt;
&lt;p&gt;A development environment can be used for substantial changes, while staging can serve as the final testing point before a release. Keeping these roles separate can help teams identify problems earlier and reduce the number of untested changes reaching production.&lt;/p&gt;
&lt;p&gt;The available configuration depends on the hosting plan and environment type. Website owners should confirm the restrictions that apply to their accounts before planning a deployment workflow.&lt;/p&gt;
&lt;p&gt;Best for: WordPress agencies, professional publishers, and businesses that require separate development and pre-release testing environments.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;WordPress.com Staging Sites&lt;br&gt;
WordPress.com includes built-in staging functionality on its Business and Commerce plans. Website owners can create a separate copy of their production site, test changes, and synchronize selected files or database content when the work is ready.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach removes the need to install and maintain a separate staging plugin for the basic staging workflow.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
A separate copy of the existing WordPress website.&lt;/p&gt;
&lt;p&gt;Testing for plugin updates, themes, and redesigns.&lt;/p&gt;
&lt;p&gt;Options to synchronize files and database content between staging and production.&lt;/p&gt;
&lt;p&gt;Support for developer access through SFTP, SSH, and WP-CLI.&lt;/p&gt;
&lt;p&gt;Staging sites that are kept out of search engine indexes.&lt;/p&gt;
&lt;p&gt;The database synchronization process deserves particular attention. Replacing the production database with the staging database can overwrite newer information, including customer details and online store orders. Website owners should review the destination data before synchronizing environments.&lt;/p&gt;
&lt;p&gt;WordPress.com is a practical option for publishers and businesses already using its managed hosting service. Its built-in staging tools can simplify testing because the environment is managed through the existing hosting dashboard.&lt;/p&gt;
&lt;p&gt;Best for: WordPress.com Business and Commerce customers who want integrated staging without a separate staging plugin.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SiteGround Staging&lt;br&gt;
SiteGround offers a WordPress staging tool through its hosting control panel. It allows eligible customers to create a separate development copy of their website, test changes, and deploy approved work to the live installation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The feature is available on GrowBig and GoGeek hosting accounts for WordPress sites. Its availability and technical restrictions should be checked before choosing a hosting plan specifically for staging.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Creation of staging copies through Site Tools.&lt;/p&gt;
&lt;p&gt;Testing of website updates and custom code.&lt;/p&gt;
&lt;p&gt;Management of staging environments.&lt;/p&gt;
&lt;p&gt;Full or selective deployment options.&lt;/p&gt;
&lt;p&gt;Restoration options for recovering from deployment problems.&lt;/p&gt;
&lt;p&gt;SiteGround's staging service is useful for WordPress owners who prefer a hosting-based workflow rather than manually copying website files and databases.&lt;/p&gt;
&lt;p&gt;Before using it, check whether the website meets the service's technical requirements. SiteGround documents restrictions involving database size, database table counts, available account resources, and WordPress Multisite compatibility.&lt;/p&gt;
&lt;p&gt;These limits matter for larger websites and installations with complex database structures.&lt;/p&gt;
&lt;p&gt;Best for: WordPress site owners using eligible SiteGround plans who need a straightforward way to test and deploy updates.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Vercel Preview Deployments&lt;br&gt;
Vercel provides preview deployments for web applications, giving developers a separate URL where they can inspect changes before releasing them to production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The platform supports deployments through connected Git repositories, its command-line interface, deployment hooks, and its API. When a supported repository is connected, new commits and pull requests can trigger deployments automatically.&lt;/p&gt;
&lt;p&gt;This makes Vercel particularly useful for development teams that want testing to become part of their normal coding and review process.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Automatic preview deployments for supported Git workflows.&lt;/p&gt;
&lt;p&gt;Unique URLs for reviewing deployed changes.&lt;/p&gt;
&lt;p&gt;Separate local, preview, and production environments.&lt;/p&gt;
&lt;p&gt;Environment-specific variables and configuration.&lt;/p&gt;
&lt;p&gt;Deployment logs and tools for inspecting build results.&lt;/p&gt;
&lt;p&gt;Options to promote a deployment to production.&lt;/p&gt;
&lt;p&gt;A developer can create a feature branch, make changes, and review the resulting deployment before merging the work into the production branch. Other team members can use the preview URL to check page layouts, application behavior, and functionality.&lt;/p&gt;
&lt;p&gt;Vercel also supports custom environments for specialized workflows, including staging and quality assurance, on eligible plans.&lt;/p&gt;
&lt;p&gt;However, preview deployments are not a substitute for every type of staging infrastructure. Applications that depend on databases, authentication services, third-party APIs, or background processing need suitable test configurations for those components.&lt;/p&gt;
&lt;p&gt;Best for: Developers building web applications and websites with Git-based deployment workflows, particularly projects that benefit from automated previews and collaborative code reviews.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Netlify Deploy Previews&lt;br&gt;
Netlify provides Deploy Previews that let teams review changes before publishing them to production. When a supported Git repository is connected, opening a pull request or merge request can trigger a preview deployment automatically.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each preview receives a separate URL, allowing developers, designers, clients, and other reviewers to inspect the proposed changes.&lt;/p&gt;
&lt;p&gt;Netlify also supports branch deployments, which provide previews associated with individual branches rather than a specific pull request.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Automated previews for supported pull request and merge request workflows.&lt;/p&gt;
&lt;p&gt;Separate URLs for reviewing proposed changes.&lt;/p&gt;
&lt;p&gt;Branch deployments for longer-running development work.&lt;/p&gt;
&lt;p&gt;Deployment contexts for configuring builds and environment variables.&lt;/p&gt;
&lt;p&gt;Collaboration features for collecting feedback.&lt;/p&gt;
&lt;p&gt;Options for restricting access to non-production deployments.&lt;/p&gt;
&lt;p&gt;Deploy Previews can reduce the time spent creating manual test environments for websites and applications that already use Netlify's deployment workflow.&lt;/p&gt;
&lt;p&gt;The platform also documents an important security consideration: preview URLs can be accessible to anyone who has the link unless appropriate access controls are configured. Netlify adds a noindex header to Deploy Previews, but branch deployments do not receive the same default indexing treatment in every case.&lt;/p&gt;
&lt;p&gt;Website owners should configure privacy and indexing controls instead of assuming every non-production URL is automatically private.&lt;/p&gt;
&lt;p&gt;Best for: Web development teams, agencies, and businesses that use Netlify and want clients or colleagues to review changes before release.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cloudflare Pages Preview Deployments&lt;br&gt;
Cloudflare Pages offers preview deployments that allow developers to test new versions of a project without deploying those changes to production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When a connected repository receives a supported pull request, Pages can generate a preview URL. Further commits update the preview, allowing reviewers to inspect the latest version of the proposed changes.&lt;/p&gt;
&lt;p&gt;This approach works well for teams that use Git-based development and want to test website changes on a deployed environment rather than relying exclusively on local testing.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Preview deployments for supported pull requests.&lt;/p&gt;
&lt;p&gt;Separate URLs for reviewing changes.&lt;/p&gt;
&lt;p&gt;Preview aliases associated with development branches.&lt;/p&gt;
&lt;p&gt;Access restrictions through Cloudflare Access.&lt;/p&gt;
&lt;p&gt;Default noindex headers on preview deployments.&lt;/p&gt;
&lt;p&gt;Integration with the Cloudflare Pages deployment workflow.&lt;/p&gt;
&lt;p&gt;Cloudflare Access can restrict preview access to authorized users. This matters when the preview contains unreleased features, internal information, or other content that should not be available to the general public.&lt;/p&gt;
&lt;p&gt;Cloudflare Pages is particularly relevant to projects hosted on its platform. It should not be confused with a complete replacement for a managed WordPress staging environment, because its deployment model is designed around the applications and projects supported by Pages.&lt;/p&gt;
&lt;p&gt;Best for: Developers and teams deploying websites through Cloudflare Pages who need isolated previews and configurable access controls.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DDEV Local Development Environments&lt;br&gt;
DDEV is an open-source tool for creating local web development environments. It supports PHP and Node.js projects and provides a workflow for managing development environments without requiring developers to build every component of the environment manually.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unlike hosted preview platforms, DDEV is primarily designed for local development. Developers can use it to build websites, test application changes, and investigate problems before deploying their work to a hosting provider.&lt;/p&gt;
&lt;p&gt;Its support for configurable, version-controlled environments makes it relevant to teams that need a repeatable development setup.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Local development environments for supported web projects.&lt;/p&gt;
&lt;p&gt;Support for PHP and Node.js.&lt;/p&gt;
&lt;p&gt;Configurable development environments.&lt;/p&gt;
&lt;p&gt;Version-controlled environment configuration.&lt;/p&gt;
&lt;p&gt;Project workflows that can be shared among developers.&lt;/p&gt;
&lt;p&gt;Integration with Docker-based development infrastructure.&lt;/p&gt;
&lt;p&gt;DDEV is useful when a developer needs to reproduce an application locally or test changes without publishing them to a public website. Teams can also use local environments to investigate errors before sending code to a shared staging server.&lt;/p&gt;
&lt;p&gt;However, local testing has limitations. A website may behave differently on the production server because of differences in PHP versions, server settings, database configuration, caching, or external services. Local development should therefore be followed by staging tests when those differences could affect the release.&lt;/p&gt;
&lt;p&gt;Best for: Developers and development teams that need repeatable local environments for supported web applications.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;WordPress Studio&lt;br&gt;
WordPress Studio is a free desktop application for local WordPress development. It allows developers to build and manage WordPress sites on their computers, work on customizations, and connect local development work with supported WordPress.com workflows.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It offers another route for developers who want to experiment with WordPress without immediately making changes to a public website.&lt;/p&gt;
&lt;p&gt;Key features&lt;br&gt;
Local WordPress site development.&lt;/p&gt;
&lt;p&gt;A desktop interface for managing local sites.&lt;/p&gt;
&lt;p&gt;Support for building and testing custom themes and plugins.&lt;/p&gt;
&lt;p&gt;Workflows for syncing with live or staging sites.&lt;/p&gt;
&lt;p&gt;Site previews hosted through WordPress.com.&lt;/p&gt;
&lt;p&gt;WordPress Studio is useful for theme development, plugin testing, redesign projects, and troubleshooting. Developers can work on a local installation before transferring appropriate changes to a shared environment for final review.&lt;/p&gt;
&lt;p&gt;Its main distinction from managed hosting staging tools is that local development happens on the developer's computer. It is not automatically a complete copy of the production hosting infrastructure, and a local installation may require additional configuration to reproduce production-specific conditions.&lt;/p&gt;
&lt;p&gt;For businesses that need client approval or team-wide testing, local development can be combined with a hosted staging environment.&lt;/p&gt;
&lt;p&gt;Best for: WordPress developers, designers, and site owners who want a desktop-based development workflow.&lt;/p&gt;
&lt;p&gt;How to Choose the Right Staging or Sandbox Tool&lt;br&gt;
The best tool is not necessarily the one with the largest feature list. It is the one that matches the website's platform and allows the team to test changes under conditions that resemble production.&lt;/p&gt;
&lt;p&gt;The following considerations can help narrow the options.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify the website platform&lt;br&gt;
Start by identifying how the website is built and hosted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A WordPress website hosted on Kinsta or WP Engine can benefit from the staging features integrated into that hosting service. WordPress.com customers can use its built-in staging functionality on eligible plans.&lt;/p&gt;
&lt;p&gt;A web application deployed through Vercel, Netlify, or Cloudflare Pages may benefit more from automated preview deployments tied to a Git repository.&lt;/p&gt;
&lt;p&gt;For developers who want to work on a computer before deploying their code, DDEV or WordPress Studio may be more appropriate.&lt;/p&gt;
&lt;p&gt;Choosing a tool that fits the existing platform can reduce the need for additional infrastructure and manual deployment work.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check how changes reach production&lt;br&gt;
Creating a staging environment is only part of the process. You also need a reliable method for moving approved changes into the live website.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check whether the platform supports full deployments, selective file transfers, database synchronization, or promotion of an existing preview deployment.&lt;/p&gt;
&lt;p&gt;This distinction matters because not every update should replace the entire production website.&lt;/p&gt;
&lt;p&gt;For example, a theme adjustment might require only a code deployment. A database replacement could overwrite customer records, new orders, comments, or other information created after the staging copy was made.&lt;/p&gt;
&lt;p&gt;Understand exactly what the deployment process will replace before approving it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Review access controls&lt;br&gt;
Staging environments can contain unpublished content, internal application features, configuration details, or copied production data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check whether the platform supports authentication, password protection, or access restrictions. Do not assume that a temporary URL is private simply because it is difficult to guess.&lt;/p&gt;
&lt;p&gt;Where a staging environment contains personal information, use appropriate access controls and avoid copying sensitive production data unless it is necessary and properly protected.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check search engine indexing&lt;br&gt;
A staging site should not compete with the production website in search results.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check the environment's indexing controls and confirm that search engines are instructed not to index the test version. Depending on the platform, this may involve HTTP response headers, access restrictions, or other configuration.&lt;/p&gt;
&lt;p&gt;A noindex directive is useful, but it should not be treated as an access-control mechanism. It does not prevent visitors from opening a publicly accessible page.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Test the complete user journey&lt;br&gt;
A page that loads correctly is not necessarily ready for production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Test the actions visitors use to complete their tasks. Depending on the website, these may include account registration, login, contact forms, checkout, search, navigation, file uploads, and payment processing.&lt;/p&gt;
&lt;p&gt;Check desktop and mobile layouts, review browser console errors, and inspect important integrations.&lt;/p&gt;
&lt;p&gt;For an online store, test the checkout process using the payment provider's supported test environment. Avoid placing unnecessary real orders merely to verify that a page works.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check backups and rollback options&lt;br&gt;
Testing reduces deployment risk, but it does not eliminate it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a current backup before major updates and confirm that the backup can be restored. Review the hosting provider's rollback options and establish a recovery procedure before deploying changes that could affect business operations.&lt;/p&gt;
&lt;p&gt;A backup is useful only if the required data can be recovered when needed.&lt;/p&gt;
&lt;p&gt;A Practical Workflow for Testing Site Updates&lt;br&gt;
Regardless of which platform you choose, a consistent release process helps reduce avoidable errors.&lt;/p&gt;
&lt;p&gt;Step 1: Create or refresh the test environment. Make sure the staging copy or local installation reflects the relevant production configuration. For database-driven websites, check whether the copied data is current enough for the planned tests.&lt;/p&gt;
&lt;p&gt;Step 2: Apply the proposed changes. Install the plugin update, modify the theme, change the application code, or adjust the configuration in the test environment.&lt;/p&gt;
&lt;p&gt;Step 3: Run functional tests. Check important pages, forms, navigation, authentication, and integrations. Confirm that the changes work on the devices and browsers relevant to your audience.&lt;/p&gt;
&lt;p&gt;Step 4: Review errors and performance. Inspect application logs, browser console messages, loading behavior, and any relevant performance measurements. Check whether the update introduces new warnings or failures.&lt;/p&gt;
&lt;p&gt;Step 5: Obtain approval. Have the responsible developer, site owner, or reviewer confirm that the changes meet the requirements. For client projects, share the preview URL or staging site with the people responsible for approval.&lt;/p&gt;
&lt;p&gt;Step 6: Back up production and deploy. Confirm that a suitable backup exists, then deploy the approved changes using the appropriate method. Review the deployment settings carefully, particularly when database synchronization is involved.&lt;/p&gt;
&lt;p&gt;Step 7: Verify the live website. After deployment, test the important user journeys again on the production site. Confirm that forms, navigation, checkout, and other essential functions remain operational.&lt;/p&gt;
&lt;p&gt;Final Verdict&lt;br&gt;
The right staging or sandbox tool depends on the website's platform, the team's development workflow, and the type of changes being tested.&lt;/p&gt;
&lt;p&gt;Kinsta, WP Engine, WordPress.com, and SiteGround are relevant options for WordPress site owners who want staging integrated with their hosting arrangements. Vercel, Netlify, and Cloudflare Pages offer preview deployment workflows for supported web projects. DDEV and WordPress Studio provide local development options for testing changes before deployment.&lt;/p&gt;
&lt;p&gt;For most website owners, the practical starting point is to use the staging or preview functionality already supported by their hosting or deployment platform. Developers with more complex requirements can combine local development, automated previews, and a dedicated staging environment.&lt;/p&gt;
&lt;p&gt;The important consideration is not simply whether a tool can create a copy of a website. It is whether the testing process can identify problems, protect production data, control access, and support a reliable deployment.&lt;/p&gt;
&lt;p&gt;A disciplined testing process helps website owners make updates with less risk of breaking important functions or disrupting visitors. It also gives developers and site administrators a clear process for reviewing changes before they reach the public website.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32892/top-staging-sandbox-tools-to-test-site-updates-before-going-live</guid>
<pubDate>Wed, 28 Oct 2026 18:04:23 +0000</pubDate>
</item>
<item>
<title>From Bug Reproduction to Playwright Test — Without Starting From Scratch</title>
<link>https://coderlegion.com/31304/from-bug-reproduction-to-playwright-test-without-starting-from-scratch</link>
<description>&lt;p&gt;Ever reproduced a bug, documented the steps, and then had to write the same steps all over again as an automated regression test?&lt;/p&gt;
&lt;p&gt;That repetitive workflow is what inspired me to build QA Bug, a Chrome extension designed to turn browser-based bug reproductions into Playwright test drafts.&lt;/p&gt;
&lt;p&gt;🛠️ How it works&lt;/p&gt;
&lt;p&gt;Reproduce the bug — Interact with the website as you normally would.&lt;/p&gt;
&lt;p&gt;Capture the steps — Record browser actions and relevant interactions.&lt;/p&gt;
&lt;p&gt;Add assertions — Define what the test should verify.&lt;/p&gt;
&lt;p&gt;Generate a Playwright test draft — Turn the recorded session into a .spec.ts file that you can review, edit, and adapt to your project.&lt;/p&gt;
&lt;p&gt;The goal isn't to replace QA engineers or magically generate perfect tests. It's to reduce repetitive work between reproducing a bug and creating a starting point for regression automation.&lt;/p&gt;
&lt;p&gt;I'm building this as a practical tool for QA engineers and developers who want to make browser testing less repetitive.&lt;/p&gt;
&lt;p&gt;💬 I'd like your feedback&lt;/p&gt;
&lt;p&gt;Would you use a tool that converts bug reproduction sessions into Playwright test drafts?&lt;/p&gt;
&lt;p&gt;What would you need before trusting the generated tests in a real project?&lt;/p&gt;
&lt;p&gt;Which part of your bug-to-regression workflow takes the most time?&lt;/p&gt;
&lt;p&gt;I'm interested in honest feedback from people who work with manual testing, Playwright, and test automation.&lt;/p&gt;
&lt;p&gt;🔧 I'd rather build something QA engineers actually need than add features nobody uses.&lt;/p&gt;
&lt;p&gt;Chrome Web Store: &lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/qa-bug-%E2%86%92-regression-test/khoelciicbkdeieibchdndccdncpelbg?authuser=1&amp;amp;hl=tr&quot;&gt;https://chromewebstore.google.com/detail/qa-bug-→-regression-test/khoelciicbkdeieibchdndccdncpelbg?authuser=1&amp;amp;hl=tr&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31304/from-bug-reproduction-to-playwright-test-without-starting-from-scratch</guid>
<pubDate>Fri, 16 Oct 2026 07:59:06 +0000</pubDate>
</item>
<item>
<title>n8n Webhook Not Working? Test, Production, and 404 Fixes</title>
<link>https://coderlegion.com/25676/n8n-webhook-not-working-test-production-and-404-fixes</link>
<description>
&lt;p&gt;
        An n8n webhook normally fails before the first workflow node runs. A 404 usually means the request did not match a registered webhook route. The common causes are a test URL with no active listener, a production URL for an unpublished workflow, the wrong HTTP method, a changed path, or an incorrect public URL on a self-hosted instance.
      &lt;/p&gt;

      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Partner disclosure.&lt;/strong&gt; This guide includes a partner link to &lt;a href=&quot;https://n8n.partnerlinks.io/6mg2xng7s1m6&quot; target=&quot;_blank&quot; rel=&quot;sponsored noopener nofollow&quot;&gt;n8n Cloud&lt;/a&gt;. If you sign up through it, I may earn a commission at no extra cost to you.&lt;/p&gt;
      &lt;/div&gt;

      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Fastest check:&lt;/strong&gt; open the Webhook node, copy the URL again, confirm whether it is Test or Production, and send the same HTTP method configured in the node. Do this before debugging credentials, expressions, or downstream nodes.&lt;/p&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;start-with-the-symptom&quot;&gt;Start with the symptom&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Symptom&lt;/th&gt;
              &lt;th&gt;Most likely check&lt;/th&gt;
              &lt;th&gt;First action&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Test URL returns 404&lt;/td&gt;
              &lt;td&gt;No current test listener&lt;/td&gt;
              &lt;td&gt;Select &lt;strong&gt;Listen for test event&lt;/strong&gt;, then resend within 120 seconds.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Production URL returns 404&lt;/td&gt;
              &lt;td&gt;Workflow is not published&lt;/td&gt;
              &lt;td&gt;Publish the workflow and copy the Production URL again.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;URL works in a browser but not from the app&lt;/td&gt;
              &lt;td&gt;GET versus POST mismatch&lt;/td&gt;
              &lt;td&gt;Match the sender's method to the Webhook node.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;URL has the wrong domain, port, or protocol&lt;/td&gt;
              &lt;td&gt;Self-hosted proxy configuration&lt;/td&gt;
              &lt;td&gt;Check &lt;code&gt;WEBHOOK_URL&lt;/code&gt;, proxy hops, and forwarded headers.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Webhook runs but no data appears on the canvas&lt;/td&gt;
              &lt;td&gt;Production execution&lt;/td&gt;
              &lt;td&gt;Open the workflow's &lt;strong&gt;Executions&lt;/strong&gt; tab.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;test-url-versus-production-url&quot;&gt;Test URL versus Production URL&lt;/h2&gt;
      &lt;p&gt;
        Every Webhook node exposes two URLs at the top of its panel. The &lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;official Webhook node documentation&lt;/a&gt; treats them as two different operating modes, not interchangeable copies of the same endpoint.
      &lt;/p&gt;

      &lt;h3&gt;Use the Test URL while building&lt;/h3&gt;
      &lt;p&gt;
        Select &lt;strong&gt;Test URL&lt;/strong&gt;, then select &lt;strong&gt;Listen for test event&lt;/strong&gt; before the external system sends its request. n8n registers the test webhook for 120 seconds and displays the incoming data in the editor. If the timer expires, register it again and resend the event.
      &lt;/p&gt;
      &lt;p&gt;
        Opening the workflow is not enough by itself. A saved test URL can return 404 later because test registration is temporary. This is useful during development because it lets one test payload appear directly on the canvas, but it is the wrong URL for a service that must call n8n at any time.
      &lt;/p&gt;

      &lt;h3&gt;Use the Production URL after publishing&lt;/h3&gt;
      &lt;p&gt;
        Select &lt;strong&gt;Production URL&lt;/strong&gt;, copy it into the sending service, and &lt;strong&gt;Publish&lt;/strong&gt; the workflow. Current n8n documentation uses Publish terminology. Publishing registers the production webhook and makes the published workflow version available to external requests until the workflow is unpublished.
      &lt;/p&gt;
      &lt;p&gt;
        Production payloads do not appear live on the editor canvas. Open &lt;strong&gt;Executions&lt;/strong&gt; to confirm whether the request created a production execution and to inspect its input. No data on the canvas does not mean the webhook failed.
      &lt;/p&gt;

      &lt;h2 id=&quot;the-n8n-webhook-404-checklist&quot;&gt;The n8n webhook 404 checklist&lt;/h2&gt;
      &lt;p&gt;Run these checks in order. Stop as soon as the request reaches n8n.&lt;/p&gt;
      &lt;ol&gt;
        &lt;li&gt;&lt;strong&gt;Copy the URL from the node again.&lt;/strong&gt; Do not rely on a URL saved in an old note, form, app, or API client.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Confirm Test or Production.&lt;/strong&gt; Test requires a temporary listener. Production requires a published workflow.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Confirm the HTTP method.&lt;/strong&gt; A POST request does not match a webhook registered only for GET.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Compare the path exactly.&lt;/strong&gt; Check spelling, hyphens, route parameters, and any path changed after the sender was configured.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Check for a path-and-method conflict.&lt;/strong&gt; n8n permits only one registered webhook for each path and method combination.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Inspect the public base URL.&lt;/strong&gt; Do this only for self-hosted n8n behind a reverse proxy or when the node displays an internal hostname, HTTP instead of HTTPS, or the wrong port.&lt;/li&gt;
      &lt;/ol&gt;

      &lt;h2 id=&quot;check-the-http-method-before-the-payload&quot;&gt;Check the HTTP method before the payload&lt;/h2&gt;
      &lt;p&gt;
        The Webhook node supports GET, POST, PUT, PATCH, DELETE, and HEAD. By default it accepts one method. The sender must use that exact method. Pasting a webhook URL into a browser sends GET, so a successful browser test does not prove that a service's POST request will match, and the reverse is also true.
      &lt;/p&gt;
      &lt;p&gt;
        Current n8n versions can allow multiple HTTP methods from the node's Settings. Use that only when the endpoint genuinely needs more than one method. A tighter single-method webhook is easier to reason about and reduces accidental calls.
      &lt;/p&gt;

      &lt;h2 id=&quot;check-the-webhook-path&quot;&gt;Check the webhook path&lt;/h2&gt;
      &lt;p&gt;
        n8n generates a random path by default, and you can replace it with a stable custom path. Once an external app stores the URL, changing the Path field changes the route it must call. Copy the complete URL after every path change instead of editing only the last segment by hand.
      &lt;/p&gt;
      &lt;p&gt;
        n8n also requires each registered path and HTTP method combination to be unique. If another published workflow already owns the same combination, unpublish the conflicting workflow or choose a different path or method. The &lt;a href=&quot;https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/common-issues/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Webhook common-issues page&lt;/a&gt; documents both method matching and route conflicts.
      &lt;/p&gt;

      &lt;h2 id=&quot;send-a-minimal-request&quot;&gt;Send a minimal request&lt;/h2&gt;
      &lt;p&gt;
        Remove the external app from the diagnosis. Copy the exact URL from the Webhook node and test it with a safe placeholder payload. The example below uses POST. Change the method if your node uses something else.
      &lt;/p&gt;
      &lt;pre class=&quot;article-code&quot;&gt;curl --request POST '&lt;a href=&quot;https://n8n.example.com/webhook/example-path&quot; rel=&quot;nofollow&quot;&gt;https://n8n.example.com/webhook/example-path&lt;/a&gt;' \
  --header 'Content-Type: application/json' \
  --data '{&quot;event&quot;:&quot;webhook-test&quot;,&quot;id&quot;:&quot;demo-001&quot;}'&lt;/pre&gt;
      &lt;p&gt;
        Replace the example URL with the full Test or Production URL copied from your node. For a Test URL, select &lt;strong&gt;Listen for test event&lt;/strong&gt; immediately before running the command. For a Production URL, publish first. If this request works, the remaining problem is in the external app's stored URL, method, headers, authentication, or network access.
      &lt;/p&gt;

      &lt;h2 id=&quot;reverse-proxy-and-public-url-problems&quot;&gt;Reverse proxy and public URL problems&lt;/h2&gt;
      &lt;p&gt;
        Skip this section on n8n Cloud. It matters when a self-hosted instance runs internally on one address but a reverse proxy exposes a different public HTTPS address.
      &lt;/p&gt;
      &lt;p&gt;
        n8n normally builds webhook URLs from &lt;code&gt;N8N_PROTOCOL&lt;/code&gt;, &lt;code&gt;N8N_HOST&lt;/code&gt;, and &lt;code&gt;N8N_PORT&lt;/code&gt;. Behind a proxy, that can produce an internal port or the wrong scheme. The &lt;a href=&quot;https://docs.n8n.io/hosting/configuration/configuration-examples/webhook-url/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;official reverse-proxy configuration&lt;/a&gt; says to set the public URL explicitly with &lt;code&gt;WEBHOOK_URL&lt;/code&gt;, set &lt;code&gt;N8N_PROXY_HOPS&lt;/code&gt; to the number of reverse proxies, and pass the forwarded host, protocol, and client information from the last proxy.
      &lt;/p&gt;
      &lt;pre class=&quot;article-code&quot;&gt;WEBHOOK_URL=&lt;a href=&quot;https://n8n.example.com/&quot; rel=&quot;nofollow&quot;&gt;https://n8n.example.com/&lt;/a&gt;
N8N_PROXY_HOPS=1&lt;/pre&gt;
      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Do not copy this blindly.&lt;/strong&gt; Use your real public HTTPS base URL and the actual number of proxies in the request path. Restart or redeploy n8n after changing environment variables, then copy the regenerated webhook URL from the node.&lt;/p&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;what-to-log-before-handing-the-webhook-into-production&quot;&gt;What to log before handing the webhook into production&lt;/h2&gt;
      &lt;ul&gt;
        &lt;li&gt;The final Production URL location and which external system owns it. Do not paste secret query parameters into a public document.&lt;/li&gt;
        &lt;li&gt;The expected HTTP method, content type, authentication method, and a sanitized example payload.&lt;/li&gt;
        &lt;li&gt;The workflow owner and where production executions are reviewed.&lt;/li&gt;
        &lt;li&gt;The expected success response and what the sender does when it receives a non-2xx response.&lt;/li&gt;
        &lt;li&gt;The alert channel and recovery owner for failures after the webhook starts the workflow.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        Once the route works, broader failures belong in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-debug-failed-workflows&quot;&gt;n8n workflow debugging guide&lt;/a&gt;. Production ownership and notifications belong in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-error-workflow-telegram-alerts&quot;&gt;n8n error workflow alerts guide&lt;/a&gt;. Keeping those jobs separate makes the webhook checklist short enough to use during an incident.
      &lt;/p&gt;

      &lt;h2 id=&quot;the-production-handoff&quot;&gt;The production handoff&lt;/h2&gt;
      &lt;ol&gt;
        &lt;li&gt;Test with the Test URL and one sanitized payload.&lt;/li&gt;
        &lt;li&gt;Switch the node to Production URL and copy the full value.&lt;/li&gt;
        &lt;li&gt;Publish the workflow.&lt;/li&gt;
        &lt;li&gt;Update the external service with the Production URL and matching method.&lt;/li&gt;
        &lt;li&gt;Trigger one real but safe event.&lt;/li&gt;
        &lt;li&gt;Confirm the run in Executions and record who owns failures.&lt;/li&gt;
      &lt;/ol&gt;
      &lt;p&gt;
        n8n autosaves edits, but saved draft changes are not the same as the published version used by production executions. The &lt;a href=&quot;https://docs.n8n.io/workflows/publish/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;save and publish documentation&lt;/a&gt; explains that production runs continue to use the current published version until you publish newer changes.
      &lt;/p&gt;

      &lt;div class=&quot;article-referral-card&quot;&gt;
        &lt;div&gt;
          &lt;p class=&quot;article-referral-kicker mono&quot;&gt;n8n referral&lt;/p&gt;
          &lt;p class=&quot;cta-h&quot;&gt;Want managed webhooks without maintaining the server?&lt;/p&gt;
          &lt;p&gt;I may earn a commission if you sign up through this link, at no extra cost to you.&lt;/p&gt;
        &lt;/div&gt;
        &lt;a href=&quot;https://n8n.partnerlinks.io/6mg2xng7s1m6&quot; target=&quot;_blank&quot; rel=&quot;sponsored noopener nofollow&quot; class=&quot;btn btn-primary article-referral-button&quot;&gt;Try n8n Cloud&lt;/a&gt;
      &lt;/div&gt;

      &lt;nav class=&quot;article-related&quot; aria-label=&quot;Related reading&quot;&gt;
        &lt;p class=&quot;article-related-h mono&quot;&gt;Keep reading&lt;/p&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-debug-failed-workflows&quot;&gt;How to Debug Failed n8n Workflows&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-error-workflow-telegram-alerts&quot;&gt;n8n Error Workflow Alerts&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/what-is-n8n-small-business&quot;&gt;What Is n8n? A Guide for Small Business&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/cases/website-uptime-monitoring-n8n&quot;&gt;Website Uptime Monitoring with n8n&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/nav&gt;

      &lt;div class=&quot;article-cta&quot;&gt;
        &lt;p class=&quot;cta-h&quot;&gt;Webhook still not reaching n8n?&lt;/p&gt;
        &lt;p&gt;Send the sanitized response, URL mode, HTTP method, and hosting setup. I will help narrow the failure to registration, routing, proxy configuration, or the sender.&lt;/p&gt;
        &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/#contact&quot; class=&quot;btn btn-primary article-cta-button&quot;&gt;Get help&lt;/a&gt;
      &lt;/div&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-webhook-not-working&quot;&gt;FloxoLab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/25676/n8n-webhook-not-working-test-production-and-404-fixes</guid>
<pubDate>Thu, 15 Oct 2026 15:50:00 +0000</pubDate>
</item>
<item>
<title>Mobile SEO Checklist: Test the Page Google and Customers See</title>
<link>https://coderlegion.com/25651/mobile-seo-checklist-test-the-page-google-and-customers-see</link>
<description>
&lt;p&gt;&lt;strong&gt;Direct answer:&lt;/strong&gt; test the exact canonical URL on a real phone and through Search Console's URL Inspection tool. Compare Google's indexed crawl with the live Google test, then confirm that the mobile page contains the complete primary content, metadata, structured data, images, and crawlable links. Complete the page's real task with touch, a small screen, and the on-screen keyboard. A responsive layout or green Lighthouse score does not prove those conditions.&lt;/p&gt;
      &lt;p&gt;Google uses the mobile version of a site's content, crawled with its smartphone agent, for indexing and ranking. There is still one search index, not a separate mobile index. The practical consequence is simple: content or links missing from the mobile result may also be missing from the evidence Google uses to understand the page.&lt;/p&gt;
      &lt;p&gt;This checklist keeps performance in its own lane. If the issue is LCP, CLS, or INP, use the separate &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/seo/guides/how-to-fix-core-web-vitals&quot;&gt;Core Web Vitals diagnosis&lt;/a&gt;. Here the question is whether the mobile page is complete, accessible, navigable, and able to finish the customer's task.&lt;/p&gt;

      &lt;h2 id=&quot;use-three-views-of-the-same-url&quot;&gt;Use three views of the same URL&lt;/h2&gt;
      &lt;p&gt;No single test shows the whole mobile experience. Audit one representative URL through these three views, then repeat the relevant checks on each important template.&lt;/p&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;View&lt;/th&gt;&lt;th&gt;What it can prove&lt;/th&gt;&lt;th&gt;What it cannot prove alone&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;URL Inspection live test&lt;/td&gt;&lt;td&gt;Google-InspectionTool can fetch the live URL, render it, and retrieve important resources&lt;/td&gt;&lt;td&gt;That Googlebot received the same result, the page is indexed, ranks, or works well for every customer&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Browser device mode&lt;/td&gt;&lt;td&gt;Responsive breakpoints, overflow, DOM content, network failures, and common device sizes&lt;/td&gt;&lt;td&gt;Real touch behavior, keyboard effects, weak connectivity, or every mobile browser&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Physical phone&lt;/td&gt;&lt;td&gt;Thumb reach, tap accuracy, menus, forms, calls, payments, and the actual business task&lt;/td&gt;&lt;td&gt;Exactly what Googlebot rendered or which resources it could crawl&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;Google retired the Mobile-Friendly Test, its API, and Search Console's Mobile Usability report on December 1, 2023. Mobile usability still matters. The useful replacement is a small test stack: URL Inspection for Google's fetch and render, browser tools for implementation evidence, and a real phone for the customer journey.&lt;/p&gt;

      &lt;h2 id=&quot;1-define-the-page-and-task-before-testing&quot;&gt;1. Define the page and task before testing&lt;/h2&gt;
      &lt;p&gt;Choose a page that represents a valuable template or action: a service page, product, category, booking flow, local landing page, lead form, or article. Record the exact clean URL and the task a mobile visitor should complete.&lt;/p&gt;
      &lt;pre class=&quot;article-code&quot;&gt;&lt;code&gt;URL: &lt;a href=&quot;https://example.com/service&quot; rel=&quot;nofollow&quot;&gt;https://example.com/service&lt;/a&gt;
Template: service detail
Primary mobile task: understand the offer and request a quote
Secondary task: call the business
Test devices: Android Chrome / iPhone Safari
Google view: URL Inspection live test
Decision: PASS / FIX / INVESTIGATE&lt;/code&gt;&lt;/pre&gt;
      &lt;p&gt;Test the whole route, not only the first screen. A restaurant page is not successful because its hero fits the viewport if the menu cannot be opened or the order button fails. An article is not complete if its comparison table or references disappear on a phone.&lt;/p&gt;

      &lt;h2 id=&quot;2-confirm-the-same-indexable-page-reaches-mobile&quot;&gt;2. Confirm the same indexable page reaches mobile&lt;/h2&gt;
      &lt;p&gt;Responsive design normally serves the same HTML at the same URL and changes the layout with CSS. Google recommends it because it is the easiest mobile pattern to implement and maintain. Dynamic serving and separate mobile URLs can work, but they create more places for content, metadata, canonicals, redirects, and links to diverge.&lt;/p&gt;
      &lt;p&gt;For the tested URL, confirm:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;the phone reaches the intended final URL instead of an irrelevant homepage or mobile error page;&lt;/li&gt;
        &lt;li&gt;the response is successful and does not depend on a device-specific redirect loop;&lt;/li&gt;
        &lt;li&gt;the robots rules, canonical, title, and meta description remain equivalent;&lt;/li&gt;
        &lt;li&gt;the page includes &lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&amp;gt;&lt;/code&gt;;&lt;/li&gt;
        &lt;li&gt;the layout fits the viewport without routine horizontal scrolling or forced zooming;&lt;/li&gt;
        &lt;li&gt;CSS, JavaScript, images, fonts, and API responses required for the primary content are not blocked.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;A desktop-only page can still be indexed if Googlebot Smartphone can access it, but that does not make it usable. Conversely, a polished mobile shell can fail indexing when it carries a different &lt;code&gt;noindex&lt;/code&gt;, canonical, empty render, or blocked resource.&lt;/p&gt;

      &lt;h2 id=&quot;3-compare-google-s-indexed-crawl-with-the-live-test&quot;&gt;3. Compare Google's indexed crawl with the live test&lt;/h2&gt;
      &lt;p&gt;Open the exact URL in Search Console and compare the indexed information with the live test. The indexed view describes Google's last processed version and reports whether it was crawled as a smartphone or desktop. The live test uses Google-InspectionTool to fetch the current page. It is useful after a fix, but it is not the same crawler as Googlebot and its pass does not mean the URL is already indexed.&lt;/p&gt;
      &lt;p&gt;After the live test completes, inspect the screenshot, rendered HTML, and loaded resources. Search the rendered HTML for the H1, a distinctive sentence from the main content, important internal links, structured data, image alt text, and the primary call to action. The screenshot is a quick visual clue, while the HTML tells you whether the information exists in the render.&lt;/p&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Symptom&lt;/th&gt;&lt;th&gt;Likely cause&lt;/th&gt;&lt;th&gt;Next check&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Blank or incomplete screenshot&lt;/td&gt;&lt;td&gt;Blocked assets, rendering error, delayed app shell, or server response difference&lt;/td&gt;&lt;td&gt;Resource list, console, server logs, rendered HTML&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Main copy absent from HTML&lt;/td&gt;&lt;td&gt;Content requires a click, swipe, typing, or later API request&lt;/td&gt;&lt;td&gt;Disable interaction and reload; inspect the initial and rendered DOM&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Images or styles missing&lt;/td&gt;&lt;td&gt;robots.txt, CDN permissions, expiring URLs, mixed content, or incorrect paths&lt;/td&gt;&lt;td&gt;Open each failed resource and compare the mobile request&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Wrong canonical or robots rule&lt;/td&gt;&lt;td&gt;Mobile template, edge cache, plugin, or environment drift&lt;/td&gt;&lt;td&gt;Compare response headers and rendered head across devices&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Navigation links absent&lt;/td&gt;&lt;td&gt;Menu items injected only after interaction or removed at the breakpoint&lt;/td&gt;&lt;td&gt;Inspect rendered anchors and the mobile navigation source&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;Do not lazy-load primary content only after user interaction. Google documents that its crawler does not swipe, click, or type to reveal that material. Content can sit inside an accordion for a compact layout, but the information should already be present and available to the rendered page.&lt;/p&gt;

      &lt;h2 id=&quot;4-compare-mobile-and-desktop-content-parity&quot;&gt;4. Compare mobile and desktop content parity&lt;/h2&gt;
      &lt;p&gt;Content parity does not mean identical pixels or identical DOM order. The mobile design can stack columns, collapse supporting sections, or change image crops. It does mean that the same page job and important evidence survive the layout change.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Primary content:&lt;/strong&gt; the answer, product details, service scope, prices or qualifiers, evidence, and next step remain available.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Headings:&lt;/strong&gt; clear and meaningful headings still describe the same sections.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Metadata:&lt;/strong&gt; title and meta description are equivalent across served versions.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Structured data:&lt;/strong&gt; the same relevant entities and correct URLs are present in the mobile render.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Images and video:&lt;/strong&gt; useful media, alt text, captions, and supporting context do not disappear.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Internal links:&lt;/strong&gt; important discovery and next-step links remain in the rendered HTML.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;Use a text or DOM comparison when dynamic serving or separate mobile URLs are involved. For a responsive site, resize the page and inspect CSS rules that hide or replace blocks. A visually hidden duplicate can also create confusion, so the goal is one complete, coherent mobile page, not two overlapping copies.&lt;/p&gt;

      &lt;h2 id=&quot;5-test-navigation-without-hover-or-guesswork&quot;&gt;5. Test navigation without hover or guesswork&lt;/h2&gt;
      &lt;p&gt;Open the menu with touch, follow each important route, use the browser Back button, and repeat with the phone rotated. The menu control should have a clear accessible name, visible focus, a correct expanded state, and a close path that does not trap the user.&lt;/p&gt;
      &lt;p&gt;Important destinations should use normal crawlable links such as &lt;code&gt;&amp;lt;a href=&quot;&lt;a href=&quot;https://floxolab.com/services&quot; rel=&quot;nofollow&quot;&gt;https://floxolab.com/services&lt;/a&gt;&quot;&amp;gt;&lt;/code&gt;. A styled &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, a click handler without a resolvable URL, or a desktop link removed from mobile navigation can make a page harder for customers and crawlers to find. Google says limiting the links in a separate mobile version can slow discovery because it indexes the mobile version.&lt;/p&gt;
      &lt;p&gt;Check the page beyond the global menu:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;breadcrumbs or back paths remain understandable;&lt;/li&gt;
        &lt;li&gt;cards have a clear destination and do not require hover to reveal it;&lt;/li&gt;
        &lt;li&gt;pagination and load-more states expose crawlable page URLs where discovery matters;&lt;/li&gt;
        &lt;li&gt;sticky headers do not cover anchored headings or the first form field;&lt;/li&gt;
        &lt;li&gt;phone, email, map, and download actions open the expected handler.&lt;/li&gt;
      &lt;/ul&gt;

      &lt;h2 id=&quot;6-check-tap-targets-zoom-and-overlays&quot;&gt;6. Check tap targets, zoom, and overlays&lt;/h2&gt;
      &lt;p&gt;Use a thumb, not a mouse cursor. Test menu icons, close buttons, filters, accordions, pagination, checkboxes, and calls to action near the screen edge. Look for accidental taps, controls that overlap, and actions that appear only after hover.&lt;/p&gt;
      &lt;p&gt;WCAG 2.2 Level AA sets a 24 by 24 CSS pixel minimum for pointer targets, with documented exceptions including sufficient spacing and inline text links. Its enhanced Level AAA target is 44 by 44 CSS pixels. Google's web.dev guidance recommends aiming around 48 device-independent pixels with space between touch controls. For frequent or consequential actions, the larger practical target is the better design goal.&lt;/p&gt;
      &lt;div class=&quot;article-callout&quot;&gt;&lt;p&gt;&lt;strong&gt;Do not disable user zoom.&lt;/strong&gt; A layout that only works by preventing pinch zoom hides an accessibility problem instead of solving it. Let the viewport fit naturally and let users enlarge content when they need to.&lt;/p&gt;&lt;/div&gt;
      &lt;p&gt;Dismiss every banner, consent dialog, chat launcher, and promotion at the tested breakpoint. A close icon must be reachable and remain visible when the on-screen keyboard opens. Google recommends banners that occupy a small portion of the screen instead of promotional interstitials that obscure the primary content.&lt;/p&gt;

      &lt;h2 id=&quot;7-complete-the-form-and-business-action&quot;&gt;7. Complete the form and business action&lt;/h2&gt;
      &lt;p&gt;A mobile SEO check is incomplete until the page does the thing it promises. Submit a test lead, book a test appointment, add a product, start checkout, call the number, open directions, or download the file. Confirm the result arrives in the real destination without duplicate submissions.&lt;/p&gt;
      &lt;p&gt;For forms, verify:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;every input has a persistent visible label and a useful error message;&lt;/li&gt;
        &lt;li&gt;email, telephone, number, date, and address fields invoke sensible mobile keyboards or pickers;&lt;/li&gt;
        &lt;li&gt;autofill and password managers do not overwrite unrelated fields;&lt;/li&gt;
        &lt;li&gt;validation does not clear correct values or jump to an invisible error;&lt;/li&gt;
        &lt;li&gt;the keyboard does not cover the active field, submit button, or error summary;&lt;/li&gt;
        &lt;li&gt;success is visible and the lead, booking, order, or message arrives once;&lt;/li&gt;
        &lt;li&gt;the action still works with a slow connection or a brief retry.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;Record operational failures separately from indexation failures. A form can be fully crawlable and still lose every mobile lead. That makes it a critical business defect even when no SEO tool raises a warning.&lt;/p&gt;

      &lt;h2 id=&quot;8-check-responsive-media-and-layout-boundaries&quot;&gt;8. Check responsive media and layout boundaries&lt;/h2&gt;
      &lt;p&gt;Inspect the page at a narrow phone width, a larger phone, and at least one intermediate width. Breakpoints often fail between the devices a designer chose for screenshots.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;text reflows without a fixed-width container pushing the page sideways;&lt;/li&gt;
        &lt;li&gt;tables wrap, scroll inside a labelled container, or transform without losing relationships;&lt;/li&gt;
        &lt;li&gt;images use an appropriate source for the viewport and retain meaningful crops;&lt;/li&gt;
        &lt;li&gt;video, maps, embeds, code blocks, and long URLs stay within the content column;&lt;/li&gt;
        &lt;li&gt;image width and height reserve the correct aspect ratio;&lt;/li&gt;
        &lt;li&gt;important text is real HTML, not trapped only inside an image.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;These checks overlap with delivery, but this guide does not grade loading performance. If large media or scripts cause slow loading, layout shift, or poor interaction responsiveness, move that evidence into the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/seo/guides/how-to-fix-core-web-vitals&quot;&gt;performance workflow&lt;/a&gt; and validate the mobile task again after the fix.&lt;/p&gt;

      &lt;h2 id=&quot;9-test-more-than-one-phone-context&quot;&gt;9. Test more than one phone context&lt;/h2&gt;
      &lt;p&gt;One device cannot represent the whole audience. Use analytics or business evidence to choose a small matrix, then include the riskiest context instead of chasing every model.&lt;/p&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;&lt;tr&gt;&lt;th&gt;Context&lt;/th&gt;&lt;th&gt;Why it matters&lt;/th&gt;&lt;th&gt;Practical test&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;&lt;td&gt;Small viewport&lt;/td&gt;&lt;td&gt;Exposes overflow, cramped controls, and sticky-overlay conflicts&lt;/td&gt;&lt;td&gt;Older or compact phone width&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;iOS and Android&lt;/td&gt;&lt;td&gt;Browsers, keyboards, date controls, and safe areas differ&lt;/td&gt;&lt;td&gt;Safari on iPhone and Chrome on Android&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Slow or unstable network&lt;/td&gt;&lt;td&gt;Late assets and retries can break the task&lt;/td&gt;&lt;td&gt;Browser throttling plus one real cellular test&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Text zoom or larger system text&lt;/td&gt;&lt;td&gt;Reveals clipped labels and fixed-height controls&lt;/td&gt;&lt;td&gt;Increase text size and repeat navigation and form tasks&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Signed out or fresh session&lt;/td&gt;&lt;td&gt;Consent, location, login, and first-visit overlays change the page&lt;/td&gt;&lt;td&gt;Private window or cleared test profile&lt;/td&gt;&lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;Prioritize representative templates and conversion routes. A &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/seo/guides/on-page-seo-audit-checklist&quot;&gt;page-level SEO audit&lt;/a&gt; can supply the intended query and page job; this mobile checklist then verifies that the same job survives the smaller screen and smartphone crawl.&lt;/p&gt;

      &lt;h2 id=&quot;what-not-to-do&quot;&gt;What not to do&lt;/h2&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Do not use a Lighthouse score as the mobile audit result.&lt;/strong&gt; It is useful evidence, not a content-parity, crawl, navigation, or conversion test.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not remove useful content just to shorten the mobile page.&lt;/strong&gt; Reorganize it with clear sections or accordions while keeping it available.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not require a click, swipe, or search to load primary indexable content.&lt;/strong&gt; Googlebot will not perform those interactions.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not block CSS, JavaScript, images, or API resources that Google needs to render the page.&lt;/strong&gt;&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not hide navigation behind non-links.&lt;/strong&gt; Important routes need resolvable &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; destinations.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not rely only on device emulation.&lt;/strong&gt; It does not reproduce a real finger, keyboard, browser chrome, app handoff, or unreliable connection.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not cover the first screen with a promotional interstitial.&lt;/strong&gt; Use a compact, dismissible banner when the message is necessary.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Do not change URLs during a responsive redesign without a real need.&lt;/strong&gt; If a move is required, use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/seo/guides/site-migration-seo-checklist&quot;&gt;site migration SEO checklist&lt;/a&gt;.&lt;/li&gt;
      &lt;/ul&gt;

      &lt;h2 id=&quot;final-mobile-seo-validation-record&quot;&gt;Final mobile SEO validation record&lt;/h2&gt;
      &lt;p&gt;Finish with observed evidence and an owner:&lt;/p&gt;
      &lt;pre class=&quot;article-code&quot;&gt;&lt;code&gt;Canonical URL and response: PASS / FIX / INVESTIGATE
Indexed mobile render and live Google test: PASS / FIX / INVESTIGATE
Primary content parity: PASS / FIX / INVESTIGATE
Metadata and structured data: PASS / FIX / INVESTIGATE
Navigation and crawlable links: PASS / FIX / INVESTIGATE
Tap targets and overlays: PASS / FIX / INVESTIGATE
Form or business task: PASS / FIX / INVESTIGATE
Responsive media and overflow: PASS / FIX / INVESTIGATE

Highest-impact defect:
Evidence and reproduction steps:
Template or page owner:
Release date:
Post-release test:&lt;/code&gt;&lt;/pre&gt;
      &lt;p&gt;Re-run the failed task after deployment on the public URL. Then use URL Inspection's live test to confirm the final Google render. For a redesign or platform move, include this record in the broader &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/seo/guides/site-migration-seo-checklist&quot;&gt;pre-launch and post-launch comparison&lt;/a&gt; so mobile parity does not disappear between environments.&lt;/p&gt;

      &lt;h2 id=&quot;sources-checked&quot;&gt;Sources checked&lt;/h2&gt;
      &lt;p&gt;Mobile indexing, rendering, responsive design, link, interstitial, and touch-target guidance was verified from Google, Chrome, web.dev, and W3C documentation on August 4, 2026.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&amp;lt;a href=&quot;&lt;a href=&quot;https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing&quot; rel=&quot;nofollow&quot;&gt;https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing&lt;/a&gt;&quot; targ&lt;/li&gt;&lt;/ul&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/25651/mobile-seo-checklist-test-the-page-google-and-customers-see</guid>
<pubDate>Sun, 20 Sep 2026 15:50:00 +0000</pubDate>
</item>
<item>
<title>n8n Free Trial: 3 Workflows to Test in 14 Days</title>
<link>https://coderlegion.com/25641/n8n-free-trial-3-workflows-to-test-in-14-days</link>
<description>
&lt;p&gt;
        &lt;strong&gt;Direct answer:&lt;/strong&gt; use the 14-day n8n Cloud trial to prove three narrow jobs, not to build a giant automation system. Keep Cloud only if at least two workflows run reliably, a named person can diagnose a failure, and the expected production executions fit the paid plan. Cancel if native tools already solve the jobs or the workflows still need constant rescue on day 14.
      &lt;/p&gt;

      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Partner disclosure.&lt;/strong&gt; This guide includes a partner link to &lt;a href=&quot;https://n8n.partnerlinks.io/6mg2xng7s1m6&quot; target=&quot;_blank&quot; rel=&quot;sponsored noopener nofollow&quot;&gt;n8n Cloud&lt;/a&gt;. If you sign up through it, I may earn a commission at no extra cost to you.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;
        This guide is a trial experiment for small-business owners and operators. It is not a broad &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/what-is-n8n-small-business&quot;&gt;introduction to n8n&lt;/a&gt;, a &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/n8n-cloud-vs-self-hosted-cost&quot;&gt;Cloud versus self-hosted comparison&lt;/a&gt;, or a complete pricing table. The job here is to leave the trial with evidence: three test records, three failure drills, an execution estimate, and a clear keep-or-cancel decision.
      &lt;/p&gt;

      &lt;h2 id=&quot;know-the-trial-limits-before-day-one&quot;&gt;Know the trial limits before day one&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Current trial fact&lt;/th&gt;
              &lt;th&gt;What it means for your test&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;14 days&lt;/td&gt;
              &lt;td&gt;Prepare sample data and app access before signup so the clock is used for testing.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;No credit card required for the Starter and Pro Cloud trial&lt;/td&gt;
              &lt;td&gt;The trial ends unless you manually subscribe. There is no ongoing free n8n Cloud plan.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Pro features under Starter compute limits&lt;/td&gt;
              &lt;td&gt;You can evaluate features such as execution search and workflow history, but not Pro-level compute capacity.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;800 AI Assistant credits during the trial&lt;/td&gt;
              &lt;td&gt;Use the preview for one controlled build. Do not make AI credits the reason the core workflows pass.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Manual executions do not use the production quota&lt;/td&gt;
              &lt;td&gt;Develop and test manually first. Activate schedules and webhooks only when acceptance checks are ready.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Official-source mismatch, checked August 8, 2026:&lt;/strong&gt; the current n8n pricing FAQ says the trial uses Starter's 2,500-execution limit, while the dedicated free-trial documentation still says 1,000 executions. Treat the quota shown inside your workspace as binding. Until it confirms otherwise, plan as if you have 1,000 production executions.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;
        Trial and plan details can change. The current pricing page also lists 320 MiB RAM and 10 millicores of burstable CPU for trial and Starter compute. That is enough for small tests, but large files, long code steps, and memory-heavy AI work can distort the result. Use small, representative records first.
      &lt;/p&gt;

      &lt;h2 id=&quot;prepare-the-evidence-before-signup&quot;&gt;Prepare the evidence before signup&lt;/h2&gt;
      &lt;p&gt;
        A useful trial begins with a small test pack. Create five dummy leads, one week of sample sales or booking rows, and six fake invoices with different due dates and statuses. Use test inboxes and test chat channels. Do not start with live customer records, automatic payment actions, or messages that can reach real customers.
      &lt;/p&gt;
      &lt;ol&gt;
        &lt;li&gt;&lt;strong&gt;Name one owner.&lt;/strong&gt; This person builds the workflow, records failures, and decides whether it passed.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Write the expected output.&lt;/strong&gt; For every sample record, state what should happen and what must not happen.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Check the native route.&lt;/strong&gt; If your form, accounting tool, or CRM already provides the needed alert or reminder, test that first.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Prepare credentials.&lt;/strong&gt; Know which test accounts need access, but add credentials through n8n's credential cards. Never paste secrets into a prompt.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Create a result sheet.&lt;/strong&gt; Record workflow, input, expected result, actual result, execution count, failure, recovery time, and decision.&lt;/li&gt;
      &lt;/ol&gt;

      &lt;h2 id=&quot;a-practical-14-day-schedule&quot;&gt;A practical 14-day schedule&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Days&lt;/th&gt;
              &lt;th&gt;Work&lt;/th&gt;
              &lt;th&gt;Evidence to keep&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Before signup&lt;/td&gt;
              &lt;td&gt;Prepare dummy data, test accounts, acceptance checks, and the result sheet.&lt;/td&gt;
              &lt;td&gt;Test pack is ready before the clock starts.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;1-2&lt;/td&gt;
              &lt;td&gt;Set the workspace timezone, connect test credentials, and run one manual trigger.&lt;/td&gt;
              &lt;td&gt;Correct timestamps and one clean credential test.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;3-5&lt;/td&gt;
              &lt;td&gt;Build and break the lead alert.&lt;/td&gt;
              &lt;td&gt;Valid, invalid, and duplicate lead results.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;6-8&lt;/td&gt;
              &lt;td&gt;Build and break the weekly summary.&lt;/td&gt;
              &lt;td&gt;Correct totals, empty-data handling, and delivery proof.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;9-11&lt;/td&gt;
              &lt;td&gt;Build and break the invoice reminder.&lt;/td&gt;
              &lt;td&gt;Due, paid, disputed, and duplicate invoice results.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;12&lt;/td&gt;
              &lt;td&gt;Run credential, timeout, duplicate, and missing-data failure drills.&lt;/td&gt;
              &lt;td&gt;Recovery steps and time to diagnose each failure.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;13&lt;/td&gt;
              &lt;td&gt;Estimate monthly production executions and name the support owner.&lt;/td&gt;
              &lt;td&gt;Normal-month and busy-month estimates.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;14&lt;/td&gt;
              &lt;td&gt;Use the scorecard, export workflows, and keep or cancel.&lt;/td&gt;
              &lt;td&gt;A written decision, not a vague intention to revisit later.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;workflow-one-route-a-qualified-lead-alert&quot;&gt;Workflow one: route a qualified lead alert&lt;/h2&gt;
      &lt;p&gt;
        Start with a form or webhook, normalize the fields, reject records missing an email or phone number, and send a notification to the person who owns follow-up. Use email, Slack, Telegram, Google Chat, or another channel your team already checks. The workflow is valuable when it filters and routes leads, not merely when it copies every form submission into another inbox.
      &lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Minimum path:&lt;/strong&gt; Form or Webhook &amp;rarr; Edit Fields &amp;rarr; If &amp;rarr; destination app.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;Send three valid leads with different service interests. Each should reach the correct owner with a timestamp and source.&lt;/li&gt;
        &lt;li&gt;Send a lead with no usable contact detail. It should enter a review route, not disappear or trigger normal follow-up.&lt;/li&gt;
        &lt;li&gt;Send the same lead twice. The owner should not receive two identical alerts if duplicates are realistic in your process.&lt;/li&gt;
        &lt;li&gt;Disable or invalidate the destination credential. Confirm that the failure is visible and that the owner knows how to retry safely.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        &lt;strong&gt;Pass:&lt;/strong&gt; five valid test leads reach the right owner within the expected time, invalid records are held for review, and a duplicate does not create duplicate work. &lt;strong&gt;Fail:&lt;/strong&gt; the team still checks the form manually because it does not trust the alert.
      &lt;/p&gt;

      &lt;h2 id=&quot;workflow-two-deliver-a-weekly-operating-summary&quot;&gt;Workflow two: deliver a weekly operating summary&lt;/h2&gt;
      &lt;p&gt;
        Use a Schedule Trigger to read a small sheet, table, or database and calculate three numbers that affect a real decision. A bakery might track orders, average order value, and unfulfilled pickups. A clinic might track new inquiries, booked appointments, and unanswered leads. Compute the numbers deterministically before asking AI to explain them.
      &lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Minimum path:&lt;/strong&gt; Schedule Trigger &amp;rarr; source app &amp;rarr; filter or aggregate &amp;rarr; email or chat.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;Run the workflow manually against a known sample and compare every total with a hand calculation.&lt;/li&gt;
        &lt;li&gt;Test an empty week. The summary should say there is no data, not invent a trend or fail silently.&lt;/li&gt;
        &lt;li&gt;Test one malformed row and one late record. Decide whether to exclude, flag, or stop.&lt;/li&gt;
        &lt;li&gt;Activate the schedule once and verify the workspace timezone, delivery time, and production execution record.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        &lt;strong&gt;Pass:&lt;/strong&gt; the owner receives the correct numbers at the correct time and can trace them to source rows. &lt;strong&gt;Fail:&lt;/strong&gt; the summary is polished but the totals cannot be reproduced.
      &lt;/p&gt;

      &lt;h2 id=&quot;workflow-three-create-a-safe-invoice-reminder&quot;&gt;Workflow three: create a safe invoice reminder&lt;/h2&gt;
      &lt;p&gt;
        First check the native reminder feature in your accounting platform. Use n8n when the job needs data from more than one system, different rules by customer or invoice, an internal approval, escalation to an account owner, or a stop condition that the native feature cannot express.
      &lt;/p&gt;
      &lt;p&gt;
        During the trial, create an internal task or a draft reminder instead of sending directly to a customer. Use fake invoices with statuses such as due soon, overdue, paid, disputed, and missing an email address. Only consider automatic external sending after someone has approved the wording, timing, opt-out handling, and stop rules.
      &lt;/p&gt;
      &lt;p&gt;&lt;strong&gt;Minimum path:&lt;/strong&gt; Schedule Trigger &amp;rarr; invoice source &amp;rarr; due-date and status checks &amp;rarr; draft or internal approval.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;A due invoice creates one draft with the correct customer, amount, invoice number, and due date.&lt;/li&gt;
        &lt;li&gt;A paid or disputed invoice creates no reminder.&lt;/li&gt;
        &lt;li&gt;A missing email creates an internal review task instead of an invalid send attempt.&lt;/li&gt;
        &lt;li&gt;Running the workflow twice does not create two reminders for the same invoice and stage.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        &lt;strong&gt;Pass:&lt;/strong&gt; the workflow applies status and duplicate safeguards before any customer-facing action. &lt;strong&gt;Fail:&lt;/strong&gt; a changed invoice status can still produce an incorrect or repeated reminder.
      &lt;/p&gt;

      &lt;h2 id=&quot;use-the-new-ai-assistant-for-one-controlled-build&quot;&gt;Use the new AI Assistant for one controlled build&lt;/h2&gt;
      &lt;p&gt;
        n8n's current AI Assistant is a Cloud-only preview that can create, edit, test, and troubleshoot workflows inside the editor. It replaces the older assistant experience as the actively developed path. Preview features can change and can produce incorrect workflows, so use it to accelerate one draft, then inspect every node and test the result yourself.
      &lt;/p&gt;
      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Example prompt:&lt;/strong&gt; Create a test workflow that receives a lead by webhook, requires either email or phone, routes valid records to my test Slack channel, sends invalid records to a review branch, and records the result. Use dummy data. Ask before activating the workflow or changing credentials.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;
        Add credentials through the credential card when asked. Do not paste API keys, passwords, or live customer data into the chat. n8n says the assistant asks for confirmation before high-impact actions, but the operator still owns permissions, message content, data exposure, and the final activation. Trial credits are token-based, so long conversations and large workflows consume more than a short, focused request.
      &lt;/p&gt;

      &lt;h2 id=&quot;understand-what-consumes-an-execution&quot;&gt;Understand what consumes an execution&lt;/h2&gt;
      &lt;p&gt;
        n8n counts a complete production workflow run as one execution, regardless of how many steps it contains. Manual runs from the editor do not count. Triggered production runs do count, including failed scheduled runs. Polling triggers count when they find new data, and each webhook request normally starts an execution. Sub-workflows and error workflows do not add separate billable executions.
      &lt;/p&gt;
      &lt;p&gt;
        Frequency changes the economics quickly. A daily summary is about 30 executions per month. A five-minute schedule is roughly 8,640 to 8,928 scheduled runs in a 30- or 31-day month, even before other workflows and retries. Use event triggers when appropriate, avoid pointless polling, and estimate both a normal month and a busy month before choosing a plan.
      &lt;/p&gt;

      &lt;h2 id=&quot;run-failure-drills-before-you-trust-the-workflows&quot;&gt;Run failure drills before you trust the workflows&lt;/h2&gt;
      &lt;p&gt;
        A successful happy-path demo only proves that one sample worked once. On day 12, break each workflow deliberately and record what the operator sees, whether partial actions can repeat, and how long recovery takes.
      &lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;Remove one required field and send an empty source.&lt;/li&gt;
        &lt;li&gt;Expire or disconnect one test credential.&lt;/li&gt;
        &lt;li&gt;Submit the same record twice.&lt;/li&gt;
        &lt;li&gt;Simulate an app timeout or rate-limit response.&lt;/li&gt;
        &lt;li&gt;Change an invoice from overdue to paid after it enters the queue.&lt;/li&gt;
        &lt;li&gt;Run a scheduled workflow across the wrong timezone boundary.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        A useful workflow exposes the failure, avoids unsafe partial repeats, and gives the owner a clear retry path. If the same error can send a second customer message, charge twice, or overwrite good data, the workflow is not ready for production.
      &lt;/p&gt;

      &lt;h2 id=&quot;use-this-keep-or-cancel-scorecard&quot;&gt;Use this keep-or-cancel scorecard&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Question&lt;/th&gt;
              &lt;th&gt;Keep signal&lt;/th&gt;
              &lt;th&gt;Cancel or redesign signal&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Did at least two workflows solve a real job?&lt;/td&gt;
              &lt;td&gt;The owner stopped a repeatable manual check or handoff.&lt;/td&gt;
              &lt;td&gt;The workflows were demos with no process owner.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Were results reliable?&lt;/td&gt;
              &lt;td&gt;Acceptance checks and failure drills passed.&lt;/td&gt;
              &lt;td&gt;The team still verifies every result manually.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Can someone support it?&lt;/td&gt;
              &lt;td&gt;A named owner can find executions, diagnose failures, and retry safely.&lt;/td&gt;
              &lt;td&gt;Only the original builder understands the workflow.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Does the usage fit?&lt;/td&gt;
              &lt;td&gt;Busy-month executions leave reasonable headroom.&lt;/td&gt;
              &lt;td&gt;Polling, retries, or volume already exceed the likely plan.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Is n8n the simplest route?&lt;/td&gt;
              &lt;td&gt;Filtering, transformation, routing, approval, or error handling justifies it.&lt;/td&gt;
              &lt;td&gt;A native feature solves the job with less maintenance.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;p&gt;
        Keep Cloud when at least two workflows pass, the support owner is clear, and the paid-plan cost is smaller than the time and errors removed. Cancel when native tools win, the workflows remain fragile, or no one will own them. A cancelled trial that prevents a poor automation purchase is still a successful test.
      &lt;/p&gt;

      &lt;h2 id=&quot;export-before-the-trial-ends&quot;&gt;Export before the trial ends&lt;/h2&gt;
      &lt;p&gt;
        There is no permanent free n8n Cloud plan. If you do not subscribe, the trial expires. n8n's current Cloud documentation says the workspace owner can download workflows from a recent backup for 90 days after the trial, after which the data is permanently deleted. Export your workflow JSON files and result sheet on day 14 instead of relying on that recovery window.
      &lt;/p&gt;
      &lt;p&gt;
        If the trial passes, activate only the workflows whose safeguards are complete. Start with one production workflow, monitor its executions, and add the next one after the owner has handled a real failure. The point is not to automate everything. It is to prove that one small system can save time without creating a larger support problem.
      &lt;/p&gt;

      &lt;h2&gt;Sources checked&lt;/h2&gt;
      &lt;p&gt;Product limits and feature behavior were verified on August 8, 2026. n8n can change trial terms, pricing, preview features, and plan limits.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;https://n8n.io/pricing/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n plans, trial limits, AI Assistant credits, and compute limits&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/deploy/use-n8n-cloud/start-your-free-trial/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n Cloud free-trial documentation&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://support.n8n.io/article/how-do-i-cancel-my-trial&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n Support: how the trial ends&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/build/ways-of-building-workflows/ai-assistant/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n AI Assistant preview, permissions, credentials, and limitations&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/build/understand-workflows/understand-executions/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n execution accounting&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/deploy/use-n8n-cloud/download-workflows/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n Cloud workflow download and deletion window&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/deploy/use-n8n-cloud/configure-cloud/manage-your-data/&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;n8n Cloud storage, memory, and compute guidance&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;

      &lt;div class=&quot;article-referral-card&quot;&gt;
        &lt;div&gt;
          &lt;p class=&quot;article-referral-kicker mono&quot;&gt;n8n partner link&lt;/p&gt;
          &lt;p class=&quot;cta-h&quot;&gt;Test one useful workflow before you automate m&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/25641/n8n-free-trial-3-workflows-to-test-in-14-days</guid>
<pubDate>Thu, 10 Sep 2026 15:50:00 +0000</pubDate>
</item>
<item>
<title>Apollo Free Trial: What You Can Test Before Paying</title>
<link>https://coderlegion.com/25636/apollo-free-trial-what-you-can-test-before-paying</link>
<description>
&lt;p&gt;
        &lt;strong&gt;Direct answer:&lt;/strong&gt; create an Apollo Free account first and do not activate the one-time 14-day paid-plan trial until your test segment is ready. Use Free to define the market and filters. During the trial, inspect 25 companies, verify 10 contacts, reveal only the data you need, test one sequence with your own inbox, and measure credit use. Upgrade only if the sample is good enough and a paid feature removes a real constraint. Otherwise, let the workspace return to Free.
      &lt;/p&gt;

      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Partner disclosure.&lt;/strong&gt; This guide includes a partner link to &lt;a href=&quot;https://get.apollo.io/jirp3swz8h6i&quot; target=&quot;_blank&quot; rel=&quot;sponsored nofollow noopener&quot;&gt;Apollo&lt;/a&gt;. If you create an account or purchase through it, I may earn a commission at no extra cost to you.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;
        This guide has one narrow job: evaluate Apollo before paying. It does not replace the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/build-b2b-prospect-list&quot;&gt;B2B prospect list guide&lt;/a&gt;, which covers ICP, company filters, buyer roles, and exclusions. If you have not chosen the research system yet, use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/apollo-vs-linkedin-sales-navigator&quot;&gt;Apollo vs LinkedIn Sales Navigator comparison&lt;/a&gt; first. Use the separate &lt;a rel=&quot;nofollow&quot; href=&quot;https://floxolab.com/blog/apollo-pricing&quot;&gt;Apollo pricing guide&lt;/a&gt; for the current plan table, credit costs, add-ons, and small-team scenarios. Here, the outcome is a written pass, upgrade, or revert decision based on one representative segment.
      &lt;/p&gt;

      &lt;h2 id=&quot;apollo-free-account-and-free-trial-are-not-the-same-thing&quot;&gt;Apollo Free account and free trial are not the same thing&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Current fact&lt;/th&gt;
              &lt;th&gt;What it means for your test&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Free account&lt;/td&gt;
              &lt;td&gt;A persistent $0 workspace for preparing filters and trying limited prospecting tools. You can keep it after the trial.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Paid-plan trial&lt;/td&gt;
              &lt;td&gt;An eligible Free account can start one 14-day trial of Basic or Professional. Custom plans do not have a self-serve trial.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;No credit card required&lt;/td&gt;
              &lt;td&gt;You can evaluate the trial without entering a card. At the end, upgrade or return to Free.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Trial credit allowance&lt;/td&gt;
              &lt;td&gt;Apollo's public pricing FAQ currently says 50 credits plus 5 mobile credits. Check the confirmation screen because Apollo is rolling out a new credit system.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Bulk action limit&lt;/td&gt;
              &lt;td&gt;You can select up to 25 records at once for a bulk save or action during the trial.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Phone and calling limits&lt;/td&gt;
              &lt;td&gt;The trial documentation lists up to 20 phone-number accesses, 60 total calling minutes, and 10 minutes per call.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;
      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Checked in a live Free workspace on August 11, 2026:&lt;/strong&gt; the account showed a $0 Free plan, 75 monthly credits, two sequences, and five AI Assistant chats. The same workspace had already used its Professional trial. Your allowance can differ during Apollo's credit-system rollout, so the numbers inside your own Billing screen are binding.&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;
        If you upgrade during the trial, Apollo says the trial ends immediately and unused trial days or credits do not carry over. You can switch between Basic and Professional during the original 14-day window, but the clock does not reset. Do the useful testing before you buy.
      &lt;/p&gt;

      &lt;h2 id=&quot;prepare-a-small-test-pack-before-starting-the-clock&quot;&gt;Prepare a small test pack before starting the clock&lt;/h2&gt;
      &lt;p&gt;
        The trial becomes valuable when you know what a good result looks like. Pick one market, one offer, and one buying role. A broad search across several industries will make the database look busy, but it will not tell you whether Apollo can support your actual outreach process.
      &lt;/p&gt;
      &lt;ol&gt;
        &lt;li&gt;&lt;strong&gt;Write the segment.&lt;/strong&gt; Define geography, industry, company size, buyer role, and at least three exclusions.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Choose 25 companies.&lt;/strong&gt; This fits the trial's bulk-selection ceiling and is large enough to expose weak filters.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Choose 10 contacts.&lt;/strong&gt; Verify their current role and company manually before spending more credits.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Name required fields.&lt;/strong&gt; Decide whether you truly need work email, phone, LinkedIn URL, company website, headcount, or technology data.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Set pass criteria.&lt;/strong&gt; For example, require at least 20 of 25 companies to fit and 8 of 10 sampled contacts to hold the expected role. These are evaluation thresholds, not Apollo accuracy claims.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Prepare one safe message.&lt;/strong&gt; Use your own or a controlled test inbox first. Do not make live cold sends the first sequence test.&lt;/li&gt;
      &lt;/ol&gt;

      &lt;h2 id=&quot;a-practical-14-day-apollo-trial-schedule&quot;&gt;A practical 14-day Apollo trial schedule&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Days&lt;/th&gt;
              &lt;th&gt;Test&lt;/th&gt;
              &lt;th&gt;Evidence to keep&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Before trial&lt;/td&gt;
              &lt;td&gt;Build the segment, exclusions, sample sheet, and pass criteria on Free.&lt;/td&gt;
              &lt;td&gt;One ready 25-company test pack.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;1-2&lt;/td&gt;
              &lt;td&gt;Rebuild the search and test filters that matter to your market.&lt;/td&gt;
              &lt;td&gt;Saved search plus notes on false positives and missing companies.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;3-5&lt;/td&gt;
              &lt;td&gt;Inspect 25 companies and 10 contacts against public company pages and professional profiles.&lt;/td&gt;
              &lt;td&gt;Fit, role, stale-data, and uncertain-data labels.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;6-7&lt;/td&gt;
              &lt;td&gt;Reveal only the email or phone data needed for the sample and review credit history.&lt;/td&gt;
              &lt;td&gt;Credits before and after each action.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;8-10&lt;/td&gt;
              &lt;td&gt;Build one sequence and send it only to a test inbox or controlled recipient.&lt;/td&gt;
              &lt;td&gt;Merge fields, timing, stop rules, unsubscribe path, and reply behavior.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;11-12&lt;/td&gt;
              &lt;td&gt;Test AI Assistant or the dialer only if either feature affects the buying decision.&lt;/td&gt;
              &lt;td&gt;Time saved, credit use, errors, and required manual review.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;13-14&lt;/td&gt;
              &lt;td&gt;Score the result, document the required plan, and upgrade or revert.&lt;/td&gt;
              &lt;td&gt;A written decision with no unused trial work left behind.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;test-one-can-apollo-isolate-the-right-market&quot;&gt;Test one: can Apollo isolate the right market?&lt;/h2&gt;
      &lt;p&gt;
        Start with company filters, then add people filters. Review the first 25 companies one by one. A company passes only when it matches the real market, not merely because Apollo assigned a nearby industry label. Record why every false positive entered the list, then tighten one filter at a time.
      &lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;Confirm the company still operates, serves the target geography, and fits the intended size range.&lt;/li&gt;
        &lt;li&gt;Check whether subsidiaries, agencies, recruiters, consultants, or competitors should be excluded.&lt;/li&gt;
        &lt;li&gt;Review job titles for function and seniority. Similar titles can represent very different buying authority.&lt;/li&gt;
        &lt;li&gt;Save the clean search only after the sample meets the written company-fit threshold.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        &lt;strong&gt;Pass:&lt;/strong&gt; the search repeatedly produces companies and roles that a human reviewer would accept. &lt;strong&gt;Fail:&lt;/strong&gt; the apparent list size collapses after manual checks or depends on exclusions you cannot express consistently.
      &lt;/p&gt;

      &lt;h2 id=&quot;test-two-measure-contact-quality-and-credit-use-together&quot;&gt;Test two: measure contact quality and credit use together&lt;/h2&gt;
      &lt;p&gt;
        Apollo's terms explicitly say its data may contain errors, duplicates, and omissions. Treat a reveal as a candidate record, not a guarantee. For 10 contacts, verify current employer and role from a public company page or professional profile. Mark records as confirmed, stale, conflicting, or uncertain.
      &lt;/p&gt;
      &lt;p&gt;
        Before revealing anything, open &lt;strong&gt;Settings &amp;gt; Billing &amp;gt; Credit usage &amp;amp; AI &amp;gt; History&lt;/strong&gt;. Record the starting balance, reveal the smallest useful sample, then inspect the history again. Apollo's current system can charge different actions for email, phone, enrichment, connected calls, AI research, exports, and other features. The confirmation shown before an action and the resulting history are more reliable than an old blog post or remembered price table.
      &lt;/p&gt;
      &lt;div class=&quot;article-callout&quot;&gt;
        &lt;p&gt;&lt;strong&gt;Do not spend the trial proving volume.&lt;/strong&gt; Ten carefully checked contacts reveal more about fit than hundreds of unreviewed records. Spend mobile credits only when phone data is part of the real workflow.&lt;/p&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;test-three-build-one-sequence-without-risking-your-domain&quot;&gt;Test three: build one sequence without risking your domain&lt;/h2&gt;
      &lt;p&gt;
        Create one short sequence for the chosen segment. Add a few controlled records that you own, review every merge field, and send to a test inbox. You are evaluating the workflow, not trying to prove that a 14-day blast can generate revenue.
      &lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;Check the sender name, reply-to address, timezone, daily schedule, and delay between steps.&lt;/li&gt;
        &lt;li&gt;Preview records with missing company names, first names, or custom fields.&lt;/li&gt;
        &lt;li&gt;Confirm what happens after a reply, bounce, unsubscribe, manual removal, or booked meeting.&lt;/li&gt;
        &lt;li&gt;Keep the first live volume low and monitor the sending mailbox outside Apollo as well.&lt;/li&gt;
        &lt;li&gt;Verify any mailbox-provider restriction shown in your workspace before connecting an important domain.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        Apollo can organize sequencing, but it cannot guarantee inbox placement, replies, meetings, or revenue. Your list quality, sender reputation, message relevance, consent basis, and local marketing rules still matter. Apollo's terms place responsibility for lawful use and verification on the customer.
      &lt;/p&gt;

      &lt;h2 id=&quot;test-ai-assistant-only-when-it-saves-a-real-step&quot;&gt;Test AI Assistant only when it saves a real step&lt;/h2&gt;
      &lt;p&gt;
        Apollo's current documentation gives Free accounts five AI Assistant chats per billing cycle and up to 10 messages in each chat. Paid access currently offers more generous use. Chatting does not automatically consume credits, and Apollo asks for confirmation before a credit-consuming action. Still, verify every output and avoid entering sensitive information.
      &lt;/p&gt;
      &lt;p&gt;
        A useful test is narrow: ask the assistant to create or refine the saved search for your written segment, then compare the result with your manual filter setup. Record whether it saved time and whether you had to correct industries, titles, exclusions, or geography. Do not upgrade because the assistant produced fluent text. Upgrade only if it reduces repeatable research work without weakening control.
      &lt;/p&gt;

      &lt;h2 id=&quot;if-calling-matters-verify-dialer-access-before-choosing-a-trial&quot;&gt;If calling matters, verify dialer access before choosing a trial&lt;/h2&gt;
      &lt;p&gt;
        Apollo's dedicated trial help page says the dialer is available only in the Professional trial. The current in-account Basic plan card we checked listed a US dialer. Because these official surfaces conflict, do not assume either is universal. Review the exact trial confirmation screen or ask Apollo support before activation if calling is part of the purchase decision.
      &lt;/p&gt;
      &lt;p&gt;
        If dialer access appears, test it with your own or an explicitly consenting recipient first. Confirm number setup, recording settings, call outcome logging, and credit use. The trial documentation lists 60 total minutes and a 10-minute limit per call, which is enough for workflow evaluation but not a calling campaign.
      &lt;/p&gt;

      &lt;h2 id=&quot;what-the-apollo-trial-cannot-prove&quot;&gt;What the Apollo trial cannot prove&lt;/h2&gt;
      &lt;ul&gt;
        &lt;li&gt;That every email, phone number, company attribute, or job title is current and accurate.&lt;/li&gt;
        &lt;li&gt;That messages will reach the inbox or that a mailbox will preserve its sender reputation.&lt;/li&gt;
        &lt;li&gt;That a segment will produce replies, meetings, pipeline, or revenue at scale.&lt;/li&gt;
        &lt;li&gt;That having access to contact data gives you permission to use it in every jurisdiction.&lt;/li&gt;
        &lt;li&gt;That the credit allowance or feature limits in your trial will remain unchanged later.&lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;
        The trial can prove something narrower and still valuable: whether Apollo finds enough plausible records for one market, whether your team can verify and organize them efficiently, whether the credit economics are visible, and whether the paid workflow is meaningfully better than staying on Free.
      &lt;/p&gt;

      &lt;h2 id=&quot;choose-basic-or-professional-by-the-feature-you-must-prove&quot;&gt;Choose Basic or Professional by the feature you must prove&lt;/h2&gt;
      &lt;p&gt;
        Start with Basic when the decision is mainly about search, saved lists, enrichment, and simple sequences. Choose Professional only when a Professional-level workflow is genuinely part of the buying decision, such as advanced sequencing, reporting, team controls, or the dialer access confirmed for your workspace. Do not select the larger trial merely to explore more menus.
      &lt;/p&gt;
      &lt;p&gt;
        Avoid activating either trial until you can name the decisive feature in one sentence. If the Free account already supports the recurring work and the paid feature does not save enough time or improve control, returning to Free is the correct outcome.
      &lt;/p&gt;

      &lt;h2 id=&quot;use-this-upgrade-or-revert-scorecard&quot;&gt;Use this upgrade-or-revert scorecard&lt;/h2&gt;
      &lt;div class=&quot;article-table-wrap&quot;&gt;
        &lt;table class=&quot;article-table&quot;&gt;
          &lt;thead&gt;
            &lt;tr&gt;
              &lt;th&gt;Question&lt;/th&gt;
              &lt;th&gt;Upgrade signal&lt;/th&gt;
              &lt;th&gt;Revert signal&lt;/th&gt;
            &lt;/tr&gt;
          &lt;/thead&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt;Did targeting pass?&lt;/td&gt;
              &lt;td&gt;The 25-company sample met the written fit threshold.&lt;/td&gt;
              &lt;td&gt;Manual review removed too much of the list.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Was contact data useful?&lt;/td&gt;
              &lt;td&gt;The verified sample supported the intended channel.&lt;/td&gt;
              &lt;td&gt;Too many records were stale, conflicting, or missing.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Were credits predictable?&lt;/td&gt;
              &lt;td&gt;The team can estimate monthly use from actual history.&lt;/td&gt;
              &lt;td&gt;Required actions consume credits too quickly or unclearly.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Did the sequence work safely?&lt;/td&gt;
              &lt;td&gt;Fields, timing, replies, and stop rules behaved as expected.&lt;/td&gt;
              &lt;td&gt;The process still needs constant manual correction.&lt;/td&gt;
            &lt;/tr&gt;
            &lt;tr&gt;
              &lt;td&gt;Did a paid feature matter?&lt;/td&gt;
              &lt;td&gt;It removed a measured bottleneck in the real process.&lt;/td&gt;
              &lt;td&gt;The Free account already covers the useful work.&lt;/td&gt;
            &lt;/tr&gt;
          &lt;/tbody&gt;
        &lt;/table&gt;
      &lt;/div&gt;

      &lt;h2 id=&quot;what-not-to-do-during-the-trial&quot;&gt;What not to do during the trial&lt;/h2&gt;
      &lt;ul&gt;
        &lt;li&gt;Do not activate before the segment, sample, and acceptance criteria are ready.&lt;/li&gt;
        &lt;li&gt;Do not reveal every available field when the workflow only needs one channel.&lt;/li&gt;
        &lt;li&gt;Do not treat database access as proof of accuracy or legal permission.&lt;/li&gt;
        &lt;li&gt;Do not test deliverability by sending a large cold campaign from a valuable domain.&lt;/li&gt;
        &lt;li&gt;Do not upgrade early. Apollo says that immediately ends the trial.&lt;/li&gt;
        &lt;li&gt;Do not judge Apollo by record count alone. Judge the verified fit and usable workflow.&lt;/li&gt;
      &lt;/ul&gt;

      &lt;h2&gt;Sources checked&lt;/h2&gt;
      &lt;p&gt;Trial rules, credit behavior, AI limits, sequence controls, and legal boundaries were checked on August 11, 2026. Apollo can change plans, allowances, and product behavior.&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;https://knowledge.apollo.io/hc/en-us/articles/5288168088205-Access-a-Free-Trial-of-Apollo&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo Help: free-trial eligibility, duration, limits, switching, and upgrade behavior&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://www.apollo.io/pricing&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo pricing FAQ: trial credits, no-card signup, and Free-plan reversion&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://www.apollo.io/on-demand-demo&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo on-demand demo: 14-day trial and no credit card requirement&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://knowledge.apollo.io/hc/en-us/articles/9527776320781-Review-Credit-Usage-in-Apollo&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo Help: credit history and credit-consuming actions&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://knowledge.apollo.io/hc/en-us/articles/39359204112397-AI-Assistant-Overview&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo Help: AI Assistant limits, confirmations, and safety guidance&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://knowledge.apollo.io/hc/en-us/articles/4409231193101-Create-a-Sequence&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo Help: creating and reviewing sequences&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://www.apollo.io/terms&quot; target=&quot;_blank&quot; rel=&quot;noopener nofollow&quot;&gt;Apollo terms: data limitations, verification, and lawful-use responsibility&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;

      &lt;div class=&quot;article-referral-card&quot;&gt;
        &lt;div&gt;
          &lt;p class=&quot;article-referral-kicker mono&quot;&gt;Apollo partner link&lt;/p&gt;
          &lt;p class=&quot;cta-h&quot;&gt;Test one qualified segment&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/25636/apollo-free-trial-what-you-can-test-before-paying</guid>
<pubDate>Sat, 05 Sep 2026 15:50:00 +0000</pubDate>
</item>
<item>
<title>Go 1.26.2 Released: Security Fixes, Regression Patches, and an Upgrade Playbook</title>
<link>https://coderlegion.com/14652/go-1-26-2-released-security-fixes-regression-patches-and-an-upgrade-playbook</link>
<description>
&lt;h1&gt;Go 1.26.2 Released: Security Fixes, Regression Patches, and an Upgrade Playbook&lt;/h1&gt;
&lt;p&gt;Go 1.26.2 was released on &lt;strong&gt;2026-04-07&lt;/strong&gt; as a patch release focused on security and stability.&lt;/p&gt;
&lt;p&gt;If you are running Go 1.26.x in production, this is the kind of release you should evaluate quickly.&lt;/p&gt;
&lt;p&gt;Go 1.26.2 includes security fixes to the &lt;code&gt;go&lt;/code&gt; command, compiler, and the &lt;code&gt;archive/tar&lt;/code&gt;, &lt;code&gt;crypto/tls&lt;/code&gt;, &lt;code&gt;crypto/x509&lt;/code&gt;, &lt;code&gt;html/template&lt;/code&gt;, and &lt;code&gt;os&lt;/code&gt; packages, plus bug fixes across the &lt;code&gt;go&lt;/code&gt; command, &lt;code&gt;go fix&lt;/code&gt;, compiler, linker, runtime, &lt;code&gt;net&lt;/code&gt;, &lt;code&gt;net/http&lt;/code&gt;, and &lt;code&gt;net/url&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-release-overview.svg&quot; alt=&quot;Go 1.26.2 release at a glance&quot;&gt;&lt;/p&gt;
&lt;h2&gt;TL;DR (beginner + technical)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you are on &lt;strong&gt;1.26.0&lt;/strong&gt; or &lt;strong&gt;1.26.1&lt;/strong&gt;, plan an upgrade to &lt;strong&gt;1.26.2&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If your service is internet-facing, treat this as high priority because security-sensitive packages were patched.&lt;/li&gt;
&lt;li&gt;Verify checksums before installation.&lt;/li&gt;
&lt;li&gt;Roll out with canary + SLO guardrails, not a full one-shot deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;If you are new: what does &quot;1.26.2&quot; mean?&lt;/h2&gt;
&lt;p&gt;Go versions follow a &lt;code&gt;major.minor.patch&lt;/code&gt; pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; = major line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;26&lt;/code&gt; = minor release line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt; = patch release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Patch releases are usually about &lt;strong&gt;fixing behavior&lt;/strong&gt;, not adding new language features.&lt;/p&gt;
&lt;h2&gt;Why this patch matters technically&lt;/h2&gt;
&lt;p&gt;Go 1.26 introduced important runtime and toolchain changes. Point releases like 1.26.2 are where real-world regressions and security backports get addressed.&lt;/p&gt;
&lt;p&gt;Use this release as both a security update and a reliability update.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Patch focus in 1.26.2&lt;/th&gt;
&lt;th&gt;Practical risk if delayed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;crypto/tls&lt;/code&gt;, &lt;code&gt;crypto/x509&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security hardening and correctness fixes&lt;/td&gt;
&lt;td&gt;TLS/cert path issues at trust boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;html/template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security-related fixes&lt;/td&gt;
&lt;td&gt;Potential template safety exposure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cmd/go&lt;/code&gt;, &lt;code&gt;go fix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toolchain reliability fixes&lt;/td&gt;
&lt;td&gt;Slow/hanging CI or broken automation flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler/linker/runtime&lt;/td&gt;
&lt;td&gt;Regression and crash fixes&lt;/td&gt;
&lt;td&gt;Build or runtime instability in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;net&lt;/code&gt;, &lt;code&gt;net/http&lt;/code&gt;, &lt;code&gt;net/url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Networking correctness fixes&lt;/td&gt;
&lt;td&gt;Subtle request/routing/parsing behavior regressions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Deep look at the Go1.26.2 milestone&lt;/h2&gt;
&lt;p&gt;Using the public GitHub milestone API for &lt;code&gt;Go1.26.2&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Milestone state: &lt;code&gt;closed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open issues: &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Closed issues: &lt;code&gt;31&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What the issue distribution tells us&lt;/h3&gt;
&lt;p&gt;From a title/label scan of closed issues, the cluster is practical and production-facing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CVE-related titles: &lt;code&gt;10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Security backport-labeled titles: &lt;code&gt;8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Compiler/runtime-labeled items: &lt;code&gt;10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmd/go&lt;/code&gt;-labeled items: &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Testing-labeled items: &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Documentation/backport consistency items: &lt;code&gt;4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the normal fingerprint of a mature patch release: security + high-impact regressions + release-quality cleanup.&lt;/p&gt;
&lt;h3&gt;Security and CVE signal&lt;/h3&gt;
&lt;p&gt;The milestone shows clear CVE and security backport activity, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78428&quot;&gt;#78428&lt;/a&gt; &lt;code&gt;security: fix CVE-2026-32283 [1.26 backport]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78426&quot;&gt;#78426&lt;/a&gt; &lt;code&gt;security: fix CVE-2026-32282 [1.26 backport]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78424&quot;&gt;#78424&lt;/a&gt; &lt;code&gt;security: fix CVE-2026-27144 [1.26 backport]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78422&quot;&gt;#78422&lt;/a&gt; &lt;code&gt;security: fix CVE-2026-27140 [1.26 backport]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78362&quot;&gt;#78362&lt;/a&gt; &lt;code&gt;crypto/x509 ... (CVE-2026-32280)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78360&quot;&gt;#78360&lt;/a&gt; &lt;code&gt;crypto/x509 ... (CVE-2026-32281)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even without changing your app code, patching core trust-boundary packages (&lt;code&gt;tls&lt;/code&gt;, &lt;code&gt;x509&lt;/code&gt;, templates, and toolchain) reduces real production risk.&lt;/p&gt;
&lt;h3&gt;Regression fixes you may actually notice&lt;/h3&gt;
&lt;p&gt;Representative examples from the milestone:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78058&quot;&gt;#78058&lt;/a&gt;: &lt;code&gt;cmd/go&lt;/code&gt; cache trim could block for 20+ minutes on macOS.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78111&quot;&gt;#78111&lt;/a&gt;: &lt;code&gt;net/url&lt;/code&gt; parsing regression affecting MongoDB multi-host connection strings.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78041&quot;&gt;#78041&lt;/a&gt;: runtime crash on Windows in 1.26.0/1.26.1.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78239&quot;&gt;#78239&lt;/a&gt;: linker panic on darwin/arm64.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/golang/go/issues/78191&quot;&gt;#78191&lt;/a&gt;: &lt;code&gt;cmd/fix&lt;/code&gt; panic in edge cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beginner translation: these are exactly the kinds of bugs that become random CI failures, odd runtime crashes, or flaky production behavior.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-milestone-breakdown.svg&quot; alt=&quot;Go 1.26.2 milestone breakdown&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Download artifacts and checksum verification&lt;/h2&gt;
&lt;p&gt;Official download page: &lt;a rel=&quot;nofollow&quot; href=&quot;https://go.dev/dl/&quot;&gt;go.dev/dl&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Selected Go 1.26.2 artifacts from the official download feed:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;SHA256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go1.26.2.src.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;33 MB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2e91ebb6947a96e9436fb2b3926a8802efe63a6d375dffec4f82aa9dbd6fd43b&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go1.26.2.linux-amd64.tar.gz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Linux x86-64&lt;/td&gt;
&lt;td&gt;64 MB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go1.26.2.darwin-arm64.pkg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;macOS Apple Silicon&lt;/td&gt;
&lt;td&gt;63 MB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5daa0b7ba59f703c5b6be2bd48437062224fd9244160e8e73a1c9f7eb8a11784&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go1.26.2.darwin-amd64.pkg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;macOS Intel&lt;/td&gt;
&lt;td&gt;66 MB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5eab5ad8943e7666554fddd72ecbcbe64cf8f04197d6e06486fbb395b779fd8d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go1.26.2.windows-amd64.msi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Windows x86-64&lt;/td&gt;
&lt;td&gt;59 MB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;84826eca833548bb2beabe7429052eaaec18faa902fde723898d906b42e59a73&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Why checksum verification matters: it confirms the artifact you downloaded is exactly what Go published.&lt;/p&gt;
&lt;p&gt;Example verification (macOS/Linux shell):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -LO &lt;a href=&quot;https://go.dev/dl/go1.26.2.linux-amd64.tar.gz&quot; rel=&quot;nofollow&quot;&gt;https://go.dev/dl/go1.26.2.linux-amd64.tar.gz&lt;/a&gt;
shasum -a 256 go1.26.2.linux-amd64.tar.gz
# expected: 990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Production-safe upgrade playbook&lt;/h2&gt;
&lt;p&gt;If your team has no formal release process yet, just follow the steps in order and keep a rollback version ready.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Use Go 1.26.2 explicitly in CI images, local dev environments, and build containers. Avoid implicit latest tags.

```dockerfile
FROM golang:1.26.2-alpine
```


Validate what your runner is actually using before tests.

```bash
go version
go env GOTOOLCHAIN GOOS GOARCH
```


Start with your existing suite, then add race detection for services that handle concurrency heavily.

```bash
go test ./...
go test ./... -race
go vet ./...
```


Execute `govulncheck` and compare findings against your previous baseline.

```bash
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
```


Add focused tests for URL parsing, HTTP handling, TLS handshakes, and cert verification in critical flows.


Canary first, then staged rollout with explicit SLO gates (error rate, latency, crash loops, build stability).


Preserve the previous working toolchain image/tag so rollback is a fast switch, not a rebuild.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.ratnesh-maurya.com/images/blog/go-1-26-2-upgrade-flow.svg&quot; alt=&quot;Go 1.26.2 upgrade flow&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Copy-paste CI gate (practical baseline)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;set -euo pipefail

go version
go env GOTOOLCHAIN GOOS GOARCH

go test ./...
go test ./... -race
go vet ./...

govulncheck ./...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Should you upgrade now?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Upgrade now (high priority):&lt;/strong&gt; internet-facing services, heavy TLS/cert usage, or strict security posture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Upgrade soon (scheduled):&lt;/strong&gt; internal services on 1.26.0/1.26.1.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan migration path:&lt;/strong&gt; older major line users that need broader compatibility testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Read more&lt;/h2&gt;
&lt;p&gt;The canonical summary of packages touched in 1.26.2.&lt;/p&gt;
&lt;p&gt;Understand what 1.26 introduced so you can interpret which 1.26.2 fixes are stabilization backports.&lt;/p&gt;
&lt;p&gt;Official binaries, source tarball, and checksums for every platform.&lt;/p&gt;
&lt;p&gt;Closed issue set showing exactly what was backported into the patch.&lt;/p&gt;
&lt;p&gt;Guidance on govulncheck, vulnerability database, and release security policy.&lt;/p&gt;
&lt;h2&gt;Data provenance&lt;/h2&gt;
&lt;p&gt;This article and its visuals are based on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Official Go release notes and release history.&lt;/li&gt;
&lt;li&gt;Official Go download index and checksums.&lt;/li&gt;
&lt;li&gt;Public GitHub milestone API data for &lt;code&gt;Go1.26.2&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The embedded images in this post are custom summary visuals created from those public sources.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/14652/go-1-26-2-released-security-fixes-regression-patches-and-an-upgrade-playbook</guid>
<pubDate>Mon, 13 Apr 2026 10:30:26 +0000</pubDate>
</item>
<item>
<title>Laravel Testing Environment Setup</title>
<link>https://coderlegion.com/13653/laravel-testing-environment-setup</link>
<description>&lt;hr&gt;
&lt;p&gt;title:  Laravel Testing Environment Setup&lt;br&gt;
published: true&lt;br&gt;
description: &lt;br&gt;
tags: #test #laravel #phpunit&lt;/p&gt;
&lt;h2 id=&quot;published-at-2026-03-26-14-38-0000&quot;&gt;published_at: 2026-03-26 14:38 +0000&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;This guide explains how to properly configure a testing environment in Laravel using .env.testing and phpunit.xml.&lt;/p&gt;
&lt;p&gt; Overview&lt;/p&gt;
&lt;p&gt;When running automated tests, you should isolate your testing environment from development and production. This prevents accidental data loss and improves test reliability.&lt;/p&gt;
&lt;p&gt;⚙️ 1. Create .env.testing&lt;/p&gt;
&lt;p&gt;Create a .env.testing file in the root of your Laravel project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-conf&quot;&gt;APP_NAME=laravel
APP_ENV=local
APP_KEY= # application key
APP_DEBUG=true
APP_URL=http://laravel.test

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file

BCRYPT_ROUNDS=12

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE= # your testing database (e.g., laravel-testing)
DB_USERNAME=root
DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_PATH=/
SESSION_DOMAIN=null

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Notes&lt;br&gt;
Use a separate database for testing.&lt;br&gt;
Never reuse your development or production database.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure phpunit.xml&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Update your phpunit.xml file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;phpunit xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:noNamespaceSchemaLocation=&quot;vendor/phpunit/phpunit/phpunit.xsd&quot;
         bootstrap=&quot;vendor/autoload.php&quot;
         colors=&quot;true&quot;
&amp;gt;
    &amp;lt;testsuites&amp;gt;
        &amp;lt;testsuite name=&quot;Unit&quot;&amp;gt;
            &amp;lt;directory&amp;gt;tests/Unit&amp;lt;/directory&amp;gt;
        &amp;lt;/testsuite&amp;gt;
        &amp;lt;testsuite name=&quot;Feature&quot;&amp;gt;
            &amp;lt;directory&amp;gt;tests/Feature&amp;lt;/directory&amp;gt;
        &amp;lt;/testsuite&amp;gt;
    &amp;lt;/testsuites&amp;gt;

    &amp;lt;source&amp;gt;
        &amp;lt;include&amp;gt;
            &amp;lt;directory&amp;gt;app&amp;lt;/directory&amp;gt;
        &amp;lt;/include&amp;gt;
    &amp;lt;/source&amp;gt;

    &amp;lt;php&amp;gt;
        &amp;lt;env name=&quot;APP_ENV&quot; value=&quot;testing&quot;/&amp;gt;
        &amp;lt;env name=&quot;APP_MAINTENANCE_DRIVER&quot; value=&quot;file&quot;/&amp;gt;
        &amp;lt;env name=&quot;BCRYPT_ROUNDS&quot; value=&quot;4&quot;/&amp;gt;

        &amp;lt;env name=&quot;BROADCAST_CONNECTION&quot; value=&quot;null&quot;/&amp;gt;
        &amp;lt;env name=&quot;CACHE_STORE&quot; value=&quot;array&quot;/&amp;gt;
        &amp;lt;env name=&quot;DB_CONNECTION&quot; value=&quot;mysql&quot;/&amp;gt;
        &amp;lt;!-- &amp;lt;env name=&quot;DB_DATABASE&quot; value=&quot;your-testing-db&quot;/&amp;gt; --&amp;gt;
        &amp;lt;env name=&quot;DB_URL&quot; value=&quot;&quot;/&amp;gt;

        &amp;lt;env name=&quot;MAIL_MAILER&quot; value=&quot;array&quot;/&amp;gt;
        &amp;lt;env name=&quot;QUEUE_CONNECTION&quot; value=&quot;sync&quot;/&amp;gt;
        &amp;lt;env name=&quot;SESSION_DRIVER&quot; value=&quot;array&quot;/&amp;gt;

        &amp;lt;env name=&quot;PULSE_ENABLED&quot; value=&quot;false&quot;/&amp;gt;
        &amp;lt;env name=&quot;TELESCOPE_ENABLED&quot; value=&quot;false&quot;/&amp;gt;
        &amp;lt;env name=&quot;NIGHTWATCH_ENABLED&quot; value=&quot;false&quot;/&amp;gt;
    &amp;lt;/php&amp;gt;
&amp;lt;/phpunit&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Run Tests&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before running tests:&lt;/p&gt;
&lt;p&gt;Ensure your testing database exists&lt;br&gt;
Run migrations if needed&lt;/p&gt;
&lt;p&gt;Then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;php artisan test

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚡ Tips&lt;br&gt;
Isolation: APP_ENV=testing ensures .env.testing is used.&lt;br&gt;
Performance:&lt;br&gt;
CACHE_STORE=array&lt;br&gt;
SESSION_DRIVER=array&lt;br&gt;
QUEUE_CONNECTION=sync&lt;br&gt;
Faster Hashing:&lt;br&gt;
Reduce BCRYPT_ROUNDS during testing&lt;br&gt;
Disable Extras:&lt;br&gt;
Disable tools like Telescope, Pulse, etc., to speed up tests&lt;/p&gt;
&lt;p&gt;✅ Summary&lt;/p&gt;
&lt;p&gt;With this setup:&lt;/p&gt;
&lt;p&gt;Tests run faster ⚡&lt;br&gt;
Data remains safe &lt;br&gt;
Environment stays isolated &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13653/laravel-testing-environment-setup</guid>
<pubDate>Sun, 29 Mar 2026 14:36:21 +0000</pubDate>
</item>
<item>
<title>Stop Building &quot;Zombie UI&quot; The Resilient UX Checklist (Playwright + Python)</title>
<link>https://coderlegion.com/8869/stop-building-zombie-ui-the-resilient-ux-checklist-playwright-python</link>
<description>&lt;p&gt;&lt;strong&gt;The Problem: The &quot;Zombie UI&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You click &quot;Submit&quot;. The database is writing data. The API is processing the request perfectly. The backend is healthy. But on the screen... &lt;strong&gt;nothing happens&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The button still looks clickable. The cursor is still a pointer. This is the &quot;&lt;strong&gt;Dead Zone&lt;/strong&gt;&quot; — the gap between the user's input and the interface's reaction.&lt;/p&gt;
&lt;p&gt;According to &lt;strong&gt;Jakob Nielsen's Response Time Limits&lt;/strong&gt;, you have a strict budget:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0 - 100ms&lt;/strong&gt;: Instant. Feels like manipulating a physical object.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100 - 300ms&lt;/strong&gt;: Slight delay. Acceptable, but noticeable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;300 - 1000ms&lt;/strong&gt;: User loses focus. &quot;Is it working? Did I miss the button?&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;gt; 1000ms: Zombie Mode&lt;/strong&gt;. The user thinks the app crashed. They will refresh the page or &lt;strong&gt;rage-click&lt;/strong&gt; the button, triggering duplicate transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A working backend is not enough. If your UI freezes for 2 seconds without feedback, &lt;strong&gt;your feature is broken&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;The Solution: The 3-Step Feedback Loop&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We are used to writing tests like this: &lt;code&gt;expect(success_message).to_be_visible()&lt;/code&gt;. But that is not enough. We must assert the &lt;strong&gt;intermediate states&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;✅ I use this Resilient UX Checklist for every async action:&lt;br&gt;
The Checklist&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Immediate (&amp;lt;100ms)&lt;/strong&gt;: The button MUST become disabled. This prevents Rage Clicks and double-charges.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Short Wait (300ms)&lt;/strong&gt;: A spinner or skeleton loader MUST appear.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Long Wait (&amp;gt;3000ms)&lt;/strong&gt;: If the network is terrible (e.g., subway tunnel), show a &quot;This is taking longer than usual...&quot; toast. Never leave the user staring at an infinite spinner.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;The Code (Python + Playwright)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How do we test this? We can't rely on random network lag. We need to deliberately freeze the request for 3 seconds to verify the application's &quot;Patience Logic&quot;.&lt;/p&gt;
&lt;p&gt;Here is a Playwright test that guarantees the &quot;Zombie UI&quot; never happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import time
from playwright.sync_api import Page, Route, expect

def test_slow_network_ux(page: Page):
    # ✅ 1. Setup the &quot;Freeze&quot; Interceptor
    def slow_handler(route: Route):
        print(f&quot;❄️ Freezing request to {route.request.url} for 3s...&quot;)
        # Simulate Bad 3G / Subway Network
        # Note: In async tests, use 'await asyncio.sleep(3)'
        time.sleep(3) 
        route.continue_()

    # Intercept the checkout API
    page.route(&quot;**/api/checkout&quot;, slow_handler)

    page.goto(&quot;/cart&quot;)

    # ✅ 2. Trigger the Action
    submit_btn = page.locator(&quot;#submit-order&quot;)
    submit_btn.click()

    # ✅ 3. Assert &quot;Immediate Feedback&quot; (0-100ms)
    # The button must be disabled immediately
    expect(submit_btn).to_be_disabled()

    # ✅ 4. Assert &quot;Loading State&quot; (100-300ms)
    # The spinner must appear while we wait (we have 3 seconds)
    spinner = page.locator(&quot;.spinner-loader&quot;)
    expect(spinner).to_be_visible()

    # ✅ 5. Assert &quot;Success State&quot; (After 3s)
    # Eventually, the request completes
    expect(page.locator(&quot;.success-message&quot;)).to_be_visible(timeout=5000)
    # The spinner should disappear
    expect(spinner).not_to_be_visible()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why automate this?&lt;/p&gt;
&lt;p&gt;If you test this manually on &lt;code&gt;localhost&lt;/code&gt;, you will blink and miss the spinner. By forcing a &lt;strong&gt;3-second delay&lt;/strong&gt; in CI, you guarantee that every user—even those on a slow mobile connection—gets a responsive UI, not a dead one.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Architecture Note: CDP vs System Proxy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why your local tests might be lying to you&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Most network interception tests (like the one above) use the &lt;strong&gt;Chrome DevTools Protocol (CDP)&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CDP is great for Browsers&lt;/strong&gt;: It gives you perfect control over traffic inside the Chrome process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CDP fails on &quot;The Full Matrix&quot;&lt;/strong&gt;: You cannot easily attach CDP to a physical iPhone running Safari, a Smart TV app, or a native Android build.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The &quot;Real World&quot; Reality&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want to run these Chaos scenarios on &lt;strong&gt;real physical devices&lt;/strong&gt; (not emulators), code-based interception isn't enough.&lt;/p&gt;
&lt;p&gt;You need a &lt;strong&gt;System Level Proxy&lt;/strong&gt; (like Charles Proxy or a cloud-native tool like &lt;a rel=&quot;nofollow&quot; href=&quot;https://chaos-proxy.debuggo.app/&quot;&gt;Chaos Proxy Debuggo&lt;/a&gt;). These tools sit between the physical device and the internet, allowing you to apply &quot;3G Throttling&quot; or &quot;Random 500 Errors&quot; to a real iPhone without changing a single line of your app's code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Perceived Performance &amp;gt; Actual Performance.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You cannot always fix the slow SQL query. You cannot fix the user's spotty 4G connection. But you can fix how your UI communicates that delay.&lt;/p&gt;
</description>
<category>QA Best Practices</category>
<guid isPermaLink="true">https://coderlegion.com/8869/stop-building-zombie-ui-the-resilient-ux-checklist-playwright-python</guid>
<pubDate>Wed, 24 Dec 2025 21:50:50 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 6): Using Testing Library Selectors</title>
<link>https://coderlegion.com/7410/testing-while-developing-part-6-using-testing-library-selectors</link>
<description>&lt;p&gt;In this part, we’ll look at a new feature in TWD: support for Testing Library–style selectors.&lt;br&gt;
These selectors are widely known in the community, and while you could technically use them before (TWD is very flexible), now they’re fully integrated — including visual selection in the &lt;strong&gt;sidebar&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;before-you-start&quot;&gt;Before You Start&lt;/h3&gt;
&lt;p&gt;If you’re following along from Part 5, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Repo git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
git reset --hard
git clean -d -f
git checkout 06-selectors-testing-library
npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;let-s-begin&quot;&gt;Let’s Begin&lt;/h3&gt;
&lt;p&gt;We’re going to migrate our current &lt;code&gt;twd.get&lt;/code&gt; calls (which uses querySelector and we were using data-testid in previous post) to the new Testing Library selectors.&lt;/p&gt;
&lt;h3 id=&quot;using-screendom&quot;&gt;Using screenDom&lt;/h3&gt;
&lt;p&gt;First, update your &lt;code&gt;src/twd-tests/helloWorld.twd.test.ts&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// We will change this
const title = await twd.get(&quot;[data-testid='welcome-title']&quot;);
// to this
const title = await screenDom.getByText(&quot;Welcome to TWD&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For assertions, we’ll use a new command: &lt;code&gt;twd.should&lt;/code&gt;.&lt;br&gt;
It works exactly like &lt;code&gt;element.should&lt;/code&gt;, but can be used with any element returned by &lt;code&gt;screenDom&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your updated test will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, userEvent, screenDom } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Hello World Page&quot;, () =&amp;gt; {
  it(&quot;should display the welcome title and counter button&quot;, async () =&amp;gt; {
    await twd.visit(&quot;/&quot;);
    
    const title = await screenDom.getByText(&quot;Welcome to TWD&quot;);
    twd.should(title, 'be.visible');
    
    const counterButton = await screenDom.getByText(&quot;Count is 0&quot;);
    twd.should(counterButton, 'be.visible');
    
    await userEvent.click(counterButton);
    twd.should(counterButton, 'have.text', 'Count is 1');
    
    await userEvent.click(counterButton);
    twd.should(counterButton, 'have.text', 'Count is 2');
    
    await userEvent.click(counterButton);
    twd.should(counterButton, 'have.text', 'Count is 3');
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;You can mix both approaches.&lt;br&gt;
&lt;code&gt;twd.get&lt;/code&gt; includes a built-in &lt;code&gt;.should&lt;/code&gt;, while screenDom lets you use familiar Testing Library selectors, combined with &lt;code&gt;twd.should&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;todo-list-tests&quot;&gt;Todo list Tests&lt;/h3&gt;
&lt;p&gt;Here’s how the Todo tests look after migrating to testing-library selectors:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, expect, userEvent, screenDom } from &quot;twd-js&quot;;
import { describe, it, beforeEach } from &quot;twd-js/runner&quot;;
import todoListMock from &quot;./mocks/todoList.json&quot;;

describe(&quot;Todo List Page&quot;, () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    twd.clearRequestMockRules();
  });

  it(&quot;should display the todo list&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock,
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    
    const todo1Title = await screenDom.getByText(&quot;Learn TWD&quot;);
    twd.should(todo1Title, &quot;be.visible&quot;);
    
    const todo2Title = await screenDom.getByText(&quot;Build Todo App&quot;);
    twd.should(todo2Title, &quot;be.visible&quot;);
    
    const todo1Description = await screenDom.getByText(&quot;Understand how to use TWD for testing web applications&quot;);
    twd.should(todo1Description, &quot;be.visible&quot;);
    
    const todo2Description = await screenDom.getByText(&quot;Create a todo list application to demonstrate TWD features&quot;);
    twd.should(todo2Description, &quot;be.visible&quot;);
    
    const todo1Date = await screenDom.getByText(&quot;Date: 2024-12-20&quot;);
    twd.should(todo1Date, &quot;be.visible&quot;);
    
    const todo2Date = await screenDom.getByText(&quot;Date: 2024-12-25&quot;);
    twd.should(todo2Date, &quot;be.visible&quot;);
  });

  it(&quot;should create a todo&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;createTodo&quot;, {
      method: &quot;POST&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock[0],
      status: 200,
    });
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: [],
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    
    const noTodosMessage = await screenDom.getByText(&quot;No todos yet. Create one above!&quot;);
    twd.should(noTodosMessage, &quot;be.visible&quot;);
    
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: [
        todoListMock[0]
      ],
      status: 200,
    });
    
    const titleInput = await screenDom.getByLabelText(&quot;Title&quot;);
    await userEvent.type(titleInput, &quot;Test Todo&quot;);
    
    const descriptionInput = await screenDom.getByLabelText(&quot;Description&quot;);
    await userEvent.type(descriptionInput, &quot;Test Description&quot;);
    
    const dateInput = await screenDom.getByLabelText(&quot;Date&quot;);
    await userEvent.type(dateInput, &quot;2024-12-20&quot;);
    
    const submitButton = await screenDom.getByRole(&quot;button&quot;, { name: &quot;Create Todo&quot; });
    await userEvent.click(submitButton);
    
    await twd.waitForRequest(&quot;getTodoList&quot;);
    const rule = await twd.waitForRequest(&quot;createTodo&quot;);
    expect(rule.request).to.deep.equal({
      title: &quot;Test Todo&quot;,
      description: &quot;Test Description&quot;,
      date: &quot;2024-12-20&quot;,
    });

    const todoList = await screenDom.getAllByText(/Learn TWD|Build Todo App|Test Todo/);
    expect(todoList).to.have.length(1);
  });

  it(&quot;should delete a todo&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;deleteTodo&quot;, {
      method: &quot;DELETE&quot;,
      url: &quot;/api/todos/1&quot;,
      response: null,
      status: 200,
    });
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock,
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
    
    // Find the delete button for the first todo (Learn TWD)
    // Since there are multiple delete buttons, we'll get all and use the first one
    // which corresponds to the first todo item
    const deleteButtons = await screenDom.getAllByRole(&quot;button&quot;, { name: &quot;Delete&quot; });
    const deleteButton = deleteButtons[0];
    
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock.filter((todo) =&amp;gt; todo.id !== &quot;1&quot;),
      status: 200,
    });
    
    await userEvent.click(deleteButton);
    await twd.waitForRequest(&quot;deleteTodo&quot;);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    
    const todoList = await screenDom.getAllByText(/Learn TWD|Build Todo App/);
    expect(todoList).to.have.length(1);
    twd.should(todoList[0], &quot;be.visible&quot;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;As you can see, TWD is flexible enough to support both styles of selectors.&lt;br&gt;
And remember: these tests run inside your actual application, so anything your app can access—stores, utilities, helpers—can be used to set up your scenarios.&lt;/p&gt;
&lt;p&gt;TWD is all about keeping testing close to development, while staying simple, flexible, and intuitive.&lt;/p&gt;
&lt;p&gt;More posts coming soon. More info in our &lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/&quot;&gt;official docs&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7410/testing-while-developing-part-6-using-testing-library-selectors</guid>
<pubDate>Sat, 15 Nov 2025 18:48:11 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 5): Collecting Coverage</title>
<link>https://coderlegion.com/7408/testing-while-developing-part-5-collecting-coverage</link>
<description>&lt;p&gt;TWD still has many more advanced features — such as function mocking — but with what we’ve covered so far, you already have everything a solid testing tool needs.&lt;br&gt;
In this step, we’ll see how to collect code coverage from your TWD tests and visualize it locally or in CI.&lt;/p&gt;
&lt;h3 id=&quot;before-you-start-1&quot;&gt;Before You Start&lt;/h3&gt;
&lt;p&gt;If you’re following along from Part 4, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Repo git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
git reset --hard
git clean -d -f
git checkout 05-coverage
npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;instrumenting-the-vite-app&quot;&gt;Instrumenting the Vite App&lt;/h3&gt;
&lt;p&gt;We’ll use the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/vite-plugin-istanbul&quot;&gt;vite-plugin-istanbul&lt;/a&gt; plugin to instrument our code and generate coverage data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i --save-dev vite-plugin-istanbul
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, open your &lt;code&gt;vite.config.ts&lt;/code&gt; and add the plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;/// &amp;lt;reference types=&quot;vitest&quot; /&amp;gt;
import path from &quot;path&quot;
import tailwindcss from &quot;@tailwindcss/vite&quot;
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// add plugin for code coverage
import istanbul from 'vite-plugin-istanbul';

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
    // configure istanbul plugin
    istanbul({
      include: 'src/**/*',
      exclude: ['node_modules', 'tests/'],
      extension: ['.ts', '.tsx'],
      requireEnv: process.env.CI ? true : false,
    }),
  ],
  resolve: {
    alias: {
      &quot;@&quot;: path.resolve(__dirname, &quot;./src&quot;),
    },
  },
  server: {
    watch: {
      ignored: [&quot;**/data/data.json&quot;, &quot;**data/routes.json&quot;],
    },
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This plugin automatically adds coverage data to &lt;code&gt;window.__coverage__&lt;/code&gt;, which means we can later extract it from Puppeteer during our CI run.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;updating-the-puppeteer-script&quot;&gt;Updating the Puppeteer Script&lt;/h3&gt;
&lt;p&gt;Let’s update our &lt;code&gt;scripts/run-tests-ci.js&lt;/code&gt; script to collect that coverage and save it locally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import fs from 'fs';
import path from 'path';
import puppeteer from &quot;puppeteer&quot;;
import { reportResults } from 'twd-js/runner-ci';

// Determine project root
let __dirname = path.resolve();

const browser = await puppeteer.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
console.time('Total Test Time');
try {
  // Navigate to your development server
  console.log('Navigating to http://localhost:5173 ...');
  await page.goto('http://localhost:5173');
  // wait to load data-testid=&quot;twd-sidebar&quot;
  await page.waitForSelector('[data-testid=&quot;twd-sidebar&quot;]', { timeout: 10000 });
  console.log('Page loaded. Starting tests...');
  // reload page
  // Execute all tests
  const { handlers, testStatus } = await page.evaluate(async () =&amp;gt; {
    const TestRunner = window.__testRunner;
    const testStatus = [];
    const runner = new TestRunner({
      onStart: () =&amp;gt; {},
      onPass: (test) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;pass&quot; });
      },
      onFail: (test, err) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;fail&quot;, error: err.message });
      },
      onSkip: (test) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;skip&quot; });
      },
    });
    const handlers = await runner.runAll();
    return { handlers: Array.from(handlers.values()), testStatus };
  });
  console.log(`Tests to report: ${testStatus.length}`);
  
  // Display results in console
  reportResults(handlers, testStatus);

  // --- Collect coverage ---
  const coverage = await page.evaluate(() =&amp;gt; window.__coverage__);
  if (coverage) {
    console.log('Collecting code coverage data...');
    const coverageDir = path.resolve(__dirname, './coverage');
    const nycDir = path.resolve(__dirname, './.nyc_output');
    if (!fs.existsSync(nycDir)) {
      fs.mkdirSync(nycDir);
    }
    if (!fs.existsSync(coverageDir)) {
      fs.mkdirSync(coverageDir);
    }
    const coveragePath = path.join(nycDir, 'out.json');
    fs.writeFileSync(coveragePath, JSON.stringify(coverage));
    console.log(`Code coverage data written to ${coveragePath}`);
  } else {
    console.log('No code coverage data found.');
  }

  // Exit with appropriate code
  const hasFailures = testStatus.some(test =&amp;gt; test.status === 'fail');
  console.timeEnd('Total Test Time');
  process.exit(hasFailures ? 1 : 0);
  
} catch (error) {
  console.error('Error running tests:', error);
  process.exit(1);
} finally {
  console.log('Closing browser...');
  await browser.close();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;updating-package-json-scripts&quot;&gt;Updating package.json Scripts&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsonc&quot;&gt;{
  &quot;scripts&quot;: {
    // ...
    &quot;dev:ci&quot;: &quot;CI=true VITE_COVERAGE=true vite&quot;,
    &quot;test:ci&quot;: &quot;node scripts/run-tests-ci.js&quot;,
    &quot;collect:coverage:html&quot;: &quot;npx nyc report --reporter=html --report-dir=coverage&quot;,
    &quot;collect:coverage:lcov&quot;: &quot;npx nyc report --reporter=lcov --report-dir=coverage&quot;,
    &quot;collect:coverage:text&quot;: &quot;npx nyc report --reporter=text --report-dir=coverage&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, run these in two terminals:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run dev:ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in another terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run test:ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the tests complete, you can generate coverage reports in different formats:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run collect:coverage:html
npm run collect:coverage:lcov
npm run collect:coverage:text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss03qfi57r43ehwgf0fn.png&quot; alt=&quot;coverage html reporter&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dtemuclax3jwbmlju5x.png&quot; alt=&quot;coverage text reporter&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;adding-coverage-to-github-actions&quot;&gt;Adding Coverage to GitHub Actions&lt;/h3&gt;
&lt;p&gt;To include the coverage output in your GitHub Action, just update your existing CI workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;name: CI - PR Tests

on:
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: 24
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Start Vite dev server
        run: |
          nohup npm run dev:ci &amp;gt; vite.log 2&amp;gt;&amp;amp;1 &amp;amp;
          npx wait-on http://localhost:5173
        env:
          CI: true

      - name: Run Puppeteer tests (test:ci)
        run: npm run test:ci
        env:
          CI: true
      
      - name: Display coverage
        run: |
          npm run collect:coverage:text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the new &lt;code&gt;Display coverage&lt;/code&gt; step, you’ll see the coverage summary directly in your GitHub Action logs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0cznd4txgvtpxz5tn10o.png&quot; alt=&quot;Github action coverage&quot;&gt;&lt;/p&gt;
&lt;p&gt;Having this basic configuration, it’s entirely up to you how you want to handle the coverage results.&lt;br&gt;
You can publish them to a service like Codecov or Coveralls, display them as badges in your README, or even use them in your CI pipeline to fail a build if coverage drops below a threshold.&lt;br&gt;
What matters is that the library gives you the flexibility to collect and track coverage directly from your browser tests, without relying on a separate test runner.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;And that’s it — we’ve reached the end of the TWD tutorial series!&lt;/p&gt;
&lt;p&gt;Throughout these five parts, we’ve built a complete workflow from zero to production-ready testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup and first visit test&lt;/li&gt;
&lt;li&gt;Selectors, assertions, and user events&lt;/li&gt;
&lt;li&gt;Network mocking&lt;/li&gt;
&lt;li&gt;CI integration with Puppeteer&lt;/li&gt;
&lt;li&gt;Code coverage collection and reporting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this foundation, you can now test your apps as you develop them, keeping your environment close to what your users actually see — which is what TWD (&lt;strong&gt;Test While Developing&lt;/strong&gt;) is all about.&lt;/p&gt;
&lt;p&gt;Thanks for following along, and happy testing!&lt;br&gt;
You can always explore more in our &lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/getting-started.html&quot;&gt;official docs&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7408/testing-while-developing-part-5-collecting-coverage</guid>
<pubDate>Sat, 15 Nov 2025 18:28:47 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 4): Running Tests in CI</title>
<link>https://coderlegion.com/7407/testing-while-developing-part-4-running-tests-in-ci</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7371/testing-while-developing-part-2-selectors-assertions-and-user-events&quot;&gt;Part 3&lt;/a&gt;, we explored one of TWD’s core features — &lt;strong&gt;network mocking&lt;/strong&gt; — and completed our first full set of tests.&lt;br&gt;
Now, it’s time to take the next step: &lt;strong&gt;running those tests in the termina&lt;/strong&gt;l so we can integrate them into a &lt;strong&gt;CI workflow&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To do this, we’ll use Puppeteer and one of TWD’s utilities, &lt;code&gt;reportResults&lt;/code&gt;, to display test results directly in the console.&lt;/p&gt;
&lt;h3 id=&quot;before-you-start-2&quot;&gt;Before You Start&lt;/h3&gt;
&lt;p&gt;If you’re following along from Part 3, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Repo git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
git reset --hard
git clean -d -f
git checkout 04-ci-integration
npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;running-tests-in-the-terminal&quot;&gt;Running Tests in the Terminal&lt;/h3&gt;
&lt;p&gt;TWD exposes its runner on the &lt;code&gt;window&lt;/code&gt; object, which means you can programmatically execute your tests from any environment — including tools like Puppeteer.&lt;/p&gt;
&lt;p&gt;Here’s the basic version of that script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { reportResults } from 'twd-js/runner-ci';

const TestRunner = window.__testRunner;
const testStatus = [];
const runner = new TestRunner({
  onStart: () =&amp;gt; {},
  onPass: (test) =&amp;gt; {
    testStatus.push({ id: test.id, status: &quot;pass&quot; });
  },
  onFail: (test, err) =&amp;gt; {
    testStatus.push({ id: test.id, status: &quot;fail&quot;, error: err.message });
  },
  onSkip: (test) =&amp;gt; {
    testStatus.push({ id: test.id, status: &quot;skip&quot; });
  },
});
const handlers = await runner.runAll();
// report results
reportResults(handlers, testStatus);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s the core logic we need to run TWD tests headlessly — but we still need a way to access the &lt;code&gt;window&lt;/code&gt; context.&lt;br&gt;
Let’s do that by using Puppeteer.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-install-dependencies&quot;&gt;Step 1. Install Dependencies&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;npm install --save-dev puppeteer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-create-a-ci-script&quot;&gt;Step 2. Create a CI Script&lt;/h3&gt;
&lt;p&gt;Let’s create a new file: &lt;code&gt;scripts/run-tests-ci.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import puppeteer from &quot;puppeteer&quot;;
import { reportResults } from 'twd-js/runner-ci';

const browser = await puppeteer.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
console.time('Total Test Time');
try {
  // Navigate to your development server
  console.log('Navigating to http://localhost:5173 ...');
  await page.goto('http://localhost:5173');
  // wait to load data-testid=&quot;twd-sidebar&quot;
  await page.waitForSelector('[data-testid=&quot;twd-sidebar&quot;]', { timeout: 10000 });
  console.log('Page loaded. Starting tests...');
  // reload page
  // Execute all tests
  const { handlers, testStatus } = await page.evaluate(async () =&amp;gt; {
    const TestRunner = window.__testRunner;
    const testStatus = [];
    const runner = new TestRunner({
      onStart: () =&amp;gt; {},
      onPass: (test) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;pass&quot; });
      },
      onFail: (test, err) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;fail&quot;, error: err.message });
      },
      onSkip: (test) =&amp;gt; {
        testStatus.push({ id: test.id, status: &quot;skip&quot; });
      },
    });
    const handlers = await runner.runAll();
    return { handlers: Array.from(handlers.values()), testStatus };
  });
  console.log(`Tests to report: ${testStatus.length}`);
  
  // Display results in console
  reportResults(handlers, testStatus);

  // Exit with appropriate code
  const hasFailures = testStatus.some(test =&amp;gt; test.status === 'fail');
  console.timeEnd('Total Test Time');
  process.exit(hasFailures ? 1 : 0);
  
} catch (error) {
  console.error('Error running tests:', error);
  process.exit(1);
} finally {
  console.log('Closing browser...');
  await browser.close();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-add-the-script-to-package-json&quot;&gt;Step 3. Add the Script to package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsonc&quot;&gt;{
  &quot;scripts&quot;: {
    // ...
    &quot;test:ci&quot;: &quot;node scripts/run-tests-ci.js&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, with your development server running in another terminal, execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run test:ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you should see something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfqyh4iuktkuy4qm8ttl.png&quot; alt=&quot;tests loaded&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;github-actions-integration&quot;&gt;GitHub Actions Integration&lt;/h3&gt;
&lt;p&gt;We’re still working on a built-in CI solution, but for now you can easily integrate TWD with GitHub Actions using Puppeteer.&lt;/p&gt;
&lt;p&gt;Create a file at &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;name: CI - PR Tests

on:
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v5

      - name: Setup Node.js
        uses: actions/setup-node@v5
        with:
          node-version: 24
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Start Vite dev server
        run: |
          nohup npm run dev &amp;gt; vite.log 2&amp;gt;&amp;amp;1 &amp;amp;
          npx wait-on http://localhost:5173
        env:
          CI: true

      - name: Run Puppeteer tests (test:ci)
        run: npm run test:ci
        env:
          CI: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it — your tests will now run automatically in CI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xny2jnyq13l6a5euy3hl.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What’s Next&lt;/h3&gt;
&lt;p&gt;We’ve learned how TWD integrates smoothly into CI workflows.&lt;br&gt;
The next step is to collect code coverage, the last missing piece of most testing setups — and that’s exactly what we’ll cover in the next post.&lt;/p&gt;
&lt;p&gt;You can always check out &lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/ci-execution.html&quot;&gt;our official docs&lt;/a&gt; to learn more.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7407/testing-while-developing-part-4-running-tests-in-ci</guid>
<pubDate>Sat, 15 Nov 2025 18:18:42 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 3): Mocking API Requests</title>
<link>https://coderlegion.com/7406/testing-while-developing-part-3-mocking-api-requests</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7371/testing-while-developing-part-2-selectors-assertions-and-user-events&quot;&gt;Part 2&lt;/a&gt;, we explored assertions, selectors, and user interactions in TWD.&lt;br&gt;
We used those tools to test the homepage, which only had a single button.&lt;/p&gt;
&lt;p&gt;Now, it’s time to move to the &lt;code&gt;/todos&lt;/code&gt; page — which brings more realistic functionality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Display Todos&lt;/li&gt;
&lt;li&gt;Create Todos&lt;/li&gt;
&lt;li&gt;Remove Todos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This page uses an API created with &lt;code&gt;json-server&lt;/code&gt;.&lt;br&gt;
We could run our tests directly against the real API like before, but that would modify local data and require resetting the database between tests. That’s not ideal — we want each test to be fully independent.&lt;/p&gt;
&lt;p&gt;That’s where mocking comes in.&lt;/p&gt;
&lt;p&gt;In TWD, we recommend mocking your network requests so you can test your frontend &lt;strong&gt;without backend dependencies&lt;/strong&gt;. This approach brings several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can simulate any scenario: success, errors, or missing data.&lt;/li&gt;
&lt;li&gt;You can validate the UX for those edge cases.&lt;/li&gt;
&lt;li&gt;You can reproduce bugs easily by mocking the exact request that caused them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To achieve this, TWD provides utilities for mocking requests using Service Workers that intercept network traffic.&lt;/p&gt;
&lt;p&gt;Let’s dive in.&lt;/p&gt;
&lt;h3 id=&quot;before-you-start-3&quot;&gt;Before You Start&lt;/h3&gt;
&lt;p&gt;If you’re following along from Part 2, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Repo git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
git reset --hard
git clean -d -f
git checkout 03-network-mocking
npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;installing-the-service-worker&quot;&gt;Installing the Service Worker&lt;/h3&gt;
&lt;p&gt;We have a command to install the Service Worker that handles request interception:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx twd-js init public --save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command creates a &lt;code&gt;mock-sw.js&lt;/code&gt; file in the folder you specify.&lt;br&gt;
Since we’re using Vite, it will be stored inside the &lt;code&gt;public&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;After that, initialize the worker in your &lt;code&gt;src/main.tsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (import.meta.env.DEV) {
  // You choose how to load the tests; this example uses Vite's glob import
  const testModules = import.meta.glob(&quot;./**/*.twd.test.ts&quot;);
  const { initTests, twd, TWDSidebar } = await import('twd-js');
  // You need to pass the test modules, the sidebar component, and createRoot function
  initTests(testModules, &amp;lt;TWDSidebar open={true} position=&quot;left&quot; /&amp;gt;, createRoot);
  // if you want to use mock requests, you can initialize it here
  twd.initRequestMocking()
    .then(() =&amp;gt; {
      console.log(&quot;Request mocking initialized&quot;);
    })
    .catch((err) =&amp;gt; {
      console.error(&quot;Error initializing request mocking:&quot;, err);
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;displaying-todos&quot;&gt;Displaying Todos&lt;/h3&gt;
&lt;p&gt;Create a new file &lt;code&gt;src/twd-tests/todoList.twd.test.ts&lt;/code&gt; and start with a basic test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Todo List Page&quot;, () =&amp;gt; {
  it(&quot;should display the todo list&quot;, async () =&amp;gt; {
    await twd.visit(&quot;/todos&quot;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test simply visits the &lt;code&gt;/todos&lt;/code&gt; page and loads the real API data:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9skcvw7fut99pzo55dwa.png&quot; alt=&quot;todo page without mock&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now let’s mock the request so we can control the response.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-twd&quot;&gt;await twd.mockRequest(&quot;getTodoList&quot;, {
  method: &quot;GET&quot;,
  url: &quot;/api/todos&quot;,
  response: [],
  status: 200,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method receives an alias and a configuration object — including the HTTP method, URL, mocked response, and status code.&lt;br&gt;
There are more options (&lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/api-mocking.html&quot;&gt;check our docs&lt;/a&gt; for all available fields).&lt;/p&gt;
&lt;p&gt;Now the complete test looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-twd&quot;&gt;import { twd } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Todo List Page&quot;, () =&amp;gt; {
  it(&quot;should display the todo list&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: [],
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will show an empty list — exactly what we defined:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cln5fy10tc7b97uj6cfx.png&quot; alt=&quot;todo page with route mocked&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is &lt;strong&gt;extremely powerful&lt;/strong&gt; because it lets you simulate all possible frontend scenarios — from successful responses to errors — without relying on your backend.&lt;br&gt;
You can even capture real requests from bugs and replay them as tests.&lt;/p&gt;
&lt;p&gt;Now let’s build a complete test with real data.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;mocking-with-real-data&quot;&gt;Mocking with Real Data&lt;/h3&gt;
&lt;p&gt;Create a mock file: &lt;code&gt;src/twd-tests/mocks/todoList.json&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &quot;id&quot;: &quot;1&quot;,
    &quot;title&quot;: &quot;Learn TWD&quot;,
    &quot;description&quot;: &quot;Understand how to use TWD for testing web applications&quot;,
    &quot;date&quot;: &quot;2024-12-20&quot;
  },
  {
    &quot;id&quot;: &quot;2&quot;,
    &quot;title&quot;: &quot;Build Todo App&quot;,
    &quot;description&quot;: &quot;Create a todo list application to demonstrate TWD features&quot;,
    &quot;date&quot;: &quot;2024-12-25&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now the complete test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;
import todoListMock from &quot;./mocks/todoList.json&quot;;

describe(&quot;Todo List Page&quot;, () =&amp;gt; {
  it(&quot;should display the todo list&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock,
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    const todo1Title = await twd.get(&quot;[data-testid='todo-title-1']&quot;);
    todo1Title.should(&quot;have.text&quot;, &quot;Learn TWD&quot;);
    const todo2Title = await twd.get(&quot;[data-testid='todo-title-2']&quot;);
    todo2Title.should(&quot;have.text&quot;, &quot;Build Todo App&quot;);
    const todo1Description = await twd.get(&quot;[data-testid='todo-description-1']&quot;);
    todo1Description.should(&quot;have.text&quot;, &quot;Understand how to use TWD for testing web applications&quot;);
    const todo2Description = await twd.get(&quot;[data-testid='todo-description-2']&quot;);
    todo2Description.should(&quot;have.text&quot;, &quot;Create a todo list application to demonstrate TWD features&quot;);
    const todo1Date = await twd.get(&quot;[data-testid='todo-date-1']&quot;);
    todo1Date.should(&quot;have.text&quot;, &quot;Date: 2024-12-20&quot;);
    const todo2Date = await twd.get(&quot;[data-testid='todo-date-2']&quot;);
    todo2Date.should(&quot;have.text&quot;, &quot;Date: 2024-12-25&quot;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new command here is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await twd.waitForRequest(&quot;getTodoList&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It waits until the mocked request is triggered — useful when you need to ensure data is rendered before asserting UI state.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Always define &lt;code&gt;mockRequest&lt;/code&gt; before triggering the request (clicks, submits, or navigation).&lt;br&gt;
If you mock it too late, it might not intercept properly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uc6wwevk534xxpcsrigi.png&quot; alt=&quot;homepage with list todos tests&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;creating-a-todo&quot;&gt;Creating a Todo&lt;/h3&gt;
&lt;p&gt;When testing the “create todo” feature, we need to verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The form is filled correctly&lt;/li&gt;
&lt;li&gt;The correct data is sent&lt;/li&gt;
&lt;li&gt;The list refreshes after submission&lt;/li&gt;
&lt;li&gt;The new element appears in the list&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s define our request mocks first:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// request of creating a todo
await twd.mockRequest(&quot;createTodo&quot;, {
  method: &quot;POST&quot;,
  url: &quot;/api/todos&quot;,
  response: todoListMock[0],
  status: 200,
});
// empty list on first load
await twd.mockRequest(&quot;getTodoList&quot;, {
  method: &quot;GET&quot;,
  url: &quot;/api/todos&quot;,
  response: [],
  status: 200,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then visit the page and confirm it’s empty:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await twd.visit(&quot;/todos&quot;);
await twd.waitForRequest(&quot;getTodoList&quot;);
const noTodosMessage = await twd.get(&quot;[data-testid='no-todos-message']&quot;);
noTodosMessage.should(&quot;be.visible&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, update the mock to include a new todo after creation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;await twd.mockRequest(&quot;getTodoList&quot;, {
  method: &quot;GET&quot;,
  url: &quot;/api/todos&quot;,
  response: [todoListMock[0]],
  status: 200,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fill and submit the form:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const title = await twd.get(&quot;input[name='title']&quot;);
await userEvent.type(title.el, &quot;Test Todo&quot;);
const description = await twd.get(&quot;input[name='description']&quot;);
await userEvent.type(description.el, &quot;Test Description&quot;);
const date = await twd.get(&quot;input[name='date']&quot;);
await userEvent.type(date.el, &quot;2024-12-20&quot;);
const submitButton = await twd.get(&quot;button[type='submit']&quot;);
// submit
await userEvent.click(submitButton.el);
// we wait for the list request to be made
await twd.waitForRequest(&quot;getTodoList&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we validate both the &lt;strong&gt;request body&lt;/strong&gt; and &lt;strong&gt;updated list&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, expect, userEvent } from &quot;twd-js&quot;;

// all waits return the rule with the definition and request made
const rule = await twd.waitForRequest(&quot;createTodo&quot;);
// we can validate the request sent to the backend
expect(rule.request).to.deep.equal({
  title: &quot;Test Todo&quot;,
  description: &quot;Test Description&quot;,
  date: &quot;2024-12-20&quot;,
});
const todoList = await twd.getAll(&quot;[data-testid='todo-item']&quot;);
expect(todoList).to.have.length(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;expect assertions (from Chai) only show up in the sidebar on failure — unlike .should which always displays. We use Chai as these tests execute in the browser&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;full-example&quot;&gt;Full Example&lt;/h3&gt;
&lt;p&gt;Here’s the complete file, including a best practice: use &lt;code&gt;twd.clearRequestMockRules()&lt;/code&gt; in a &lt;code&gt;beforeEach()&lt;/code&gt; to ensure every test runs independently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, expect, userEvent } from &quot;twd-js&quot;;
import { describe, it, beforeEach } from &quot;twd-js/runner&quot;;
import todoListMock from &quot;./mocks/todoList.json&quot;;

describe(&quot;Todo List Page&quot;, () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    twd.clearRequestMockRules();
  });

  // ... display test ...

  it(&quot;should create a todo&quot;, async () =&amp;gt; {
    await twd.mockRequest(&quot;createTodo&quot;, {
      method: &quot;POST&quot;,
      url: &quot;/api/todos&quot;,
      response: todoListMock[0],
      status: 200,
    });
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: [],
      status: 200,
    });
    await twd.visit(&quot;/todos&quot;);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    await twd.mockRequest(&quot;getTodoList&quot;, {
      method: &quot;GET&quot;,
      url: &quot;/api/todos&quot;,
      response: [todoListMock[0]],
      status: 200,
    });
    const noTodosMessage = await twd.get(&quot;[data-testid='no-todos-message']&quot;);
    noTodosMessage.should(&quot;be.visible&quot;);
    const title = await twd.get(&quot;input[name='title']&quot;);
    await userEvent.type(title.el, &quot;Test Todo&quot;);
    const description = await twd.get(&quot;input[name='description']&quot;);
    await userEvent.type(description.el, &quot;Test Description&quot;);
    const date = await twd.get(&quot;input[name='date']&quot;);
    await userEvent.type(date.el, &quot;2024-12-20&quot;);
    const submitButton = await twd.get(&quot;button[type='submit']&quot;);
    await userEvent.click(submitButton.el);
    await twd.waitForRequest(&quot;getTodoList&quot;);
    const rule = await twd.waitForRequest(&quot;createTodo&quot;);
    expect(rule.request).to.deep.equal({
      title: &quot;Test Todo&quot;,
      description: &quot;Test Description&quot;,
      date: &quot;2024-12-20&quot;,
    });
    const todoList = await twd.getAll(&quot;[data-testid='todo-item']&quot;);
    expect(todoList).to.have.length(1);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qd33cy9ky9ir6f3ztio.png&quot; alt=&quot;All tests passed&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;removing-todos&quot;&gt;Removing Todos&lt;/h3&gt;
&lt;p&gt;For deleting todos, we reuse what we’ve learned — mocking, waiting, and validating.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;it(&quot;should delete a todo&quot;, async () =&amp;gt; {
  await twd.mockRequest(&quot;deleteTodo&quot;, {
    method: &quot;DELETE&quot;,
    url: &quot;/api/todos/1&quot;,
    response: null,
    status: 200,
  });
  await twd.mockRequest(&quot;getTodoList&quot;, {
    method: &quot;GET&quot;,
    url: &quot;/api/todos&quot;,
    response: todoListMock,
    status: 200,
  });
  await twd.visit(&quot;/todos&quot;);
  const deleteButton = await twd.get(&quot;[data-testid='delete-todo-1']&quot;);
  await twd.mockRequest(&quot;getTodoList&quot;, {
    method: &quot;GET&quot;,
    url: &quot;/api/todos&quot;,
    response: todoListMock.filter((todo) =&amp;gt; todo.id !== &quot;1&quot;),
    status: 200,
  });
  await userEvent.click(deleteButton.el);
  await twd.waitForRequest(&quot;deleteTodo&quot;);
  await twd.waitForRequest(&quot;getTodoList&quot;);
  const todoList = await twd.getAll(&quot;[data-testid='todo-item']&quot;);
  expect(todoList).to.have.length(1);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next-1&quot;&gt;What's next&lt;/h3&gt;
&lt;p&gt;We’ve covered one of the most important parts of the TWD approach — mocking.&lt;br&gt;
This is where the framework truly shines.&lt;/p&gt;
&lt;p&gt;A few closing thoughts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use mocks to test what you’re building — your frontend.&lt;br&gt;
Don’t worry about backend or third-party services during development.&lt;/li&gt;
&lt;li&gt;These mocks only intercept &lt;strong&gt;client-side&lt;/strong&gt; requests (SSR is out of scope).&lt;/li&gt;
&lt;li&gt;You’ll have all possible scenarios documented as runnable tests — no setup, no servers, just &lt;code&gt;npm i&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We strongly believe this is the most efficient way to develop and test modern SPAs.&lt;br&gt;
It keeps your workflow fast, predictable, and resilient to backend changes.&lt;/p&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7407/testing-while-developing-part-4-running-tests-in-ci&quot;&gt;next post&lt;/a&gt;, we’ll take things further by adding &lt;strong&gt;terminal execution for CI&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7406/testing-while-developing-part-3-mocking-api-requests</guid>
<pubDate>Sat, 15 Nov 2025 18:16:54 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 2): Selectors, Assertions, and User Events</title>
<link>https://coderlegion.com/7371/testing-while-developing-part-2-selectors-assertions-and-user-events</link>
<description>&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7370/testing-while-developing-part-1-installation-and-first-test&quot;&gt;previous post&lt;/a&gt;, we set up our basic testing configuration and used the &lt;code&gt;visit&lt;/code&gt; command to load our app.&lt;br&gt;
Now, we’re ready to make our tests a little more meaningful.&lt;/p&gt;
&lt;p&gt;In this part, we’ll focus on three key features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Selectors – how we find elements in our UI&lt;/li&gt;
&lt;li&gt;Assertions – how we verify what’s on screen&lt;/li&gt;
&lt;li&gt;User events – how we simulate real interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s dive in.&lt;/p&gt;
&lt;h3 id=&quot;before-you-start-4&quot;&gt;Before You Start&lt;/h3&gt;
&lt;p&gt;If you’re following along from Part 1, you can continue as is. But if you want to reset your repo or make sure you're on the correct branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Repo git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
git reset --hard
git clean -d -f
git checkout 02-assertions
npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;selectors&quot;&gt;Selectors&lt;/h3&gt;
&lt;p&gt;TWD uses a simple and familiar approach for selectors.&lt;br&gt;
It provides two commands — &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;getAll&lt;/code&gt; — which are based directly on the native DOM APIs &lt;code&gt;document.querySelector&lt;/code&gt; and &lt;code&gt;document.querySelectorAll&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That means you can use any selector you’d normally use in the browser: class, id, tag, attribute, role, etc.&lt;br&gt;
We believe this keeps things flexible and intuitive, especially for developers already comfortable with the DOM.&lt;/p&gt;
&lt;p&gt;Let’s improve our existing test file &lt;code&gt;src/twd-tests/helloWorld.twd.test.ts&lt;/code&gt;.&lt;br&gt;
We’ll test that our title displays the text “Welcome to TWD” and that our counter button updates as expected.&lt;/p&gt;
&lt;p&gt;We’ll start by selecting the elements using twd.get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const title = await twd.get(&quot;[data-testid='welcome-title']&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we’re using a &lt;code&gt;data-testid&lt;/code&gt; attribute — just like in React Testing Library or Cypress.&lt;br&gt;
Because &lt;code&gt;get&lt;/code&gt; is based on &lt;code&gt;querySelector&lt;/code&gt;, you can use any CSS selector you prefer.&lt;/p&gt;
&lt;p&gt;For the counter button, we can do the same:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const counterButton = await twd.get(&quot;[data-testid='counter-button']&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;Note: Selectors are async because TWD automatically retries finding the element for up to two seconds.&lt;br&gt;
So don’t forget to use &lt;code&gt;await&lt;/code&gt; before them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;assertions&quot;&gt;Assertions&lt;/h3&gt;
&lt;p&gt;Once we’ve selected our elements, it’s time to verify that our UI behaves as expected.&lt;/p&gt;
&lt;p&gt;Each selected element returned by &lt;code&gt;twd.get&lt;/code&gt; includes two useful properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;el&lt;/code&gt; – the raw DOM element&lt;/li&gt;
&lt;li&gt;&lt;code&gt;should&lt;/code&gt; – a utility for performing assertions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s how we can assert visibility and text content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const title = await twd.get(&quot;[data-testid='welcome-title']&quot;);
title.should(&quot;be.visible&quot;).should(&quot;have.text&quot;, &quot;Welcome to TWD&quot;);

const counterButton = await twd.get(&quot;[data-testid='counter-button']&quot;);
counterButton.should(&quot;be.visible&quot;).should(&quot;have.text&quot;, &quot;Count is 0&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The should command is chainable, so you can stack multiple conditions easily.&lt;br&gt;
TWD supports several built-in assertions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;be.visible&lt;/li&gt;
&lt;li&gt;have.text&lt;/li&gt;
&lt;li&gt;have.class&lt;/li&gt;
&lt;li&gt;have.attribute&lt;/li&gt;
&lt;li&gt;and even their &lt;code&gt;not&lt;/code&gt; versions (e.g. &lt;code&gt;should(&quot;not.have.text&quot;, &quot;Error&quot;)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you run your tests, the sidebar will clearly show what was tested:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6cpsujkux94p9jzfw1a8.png&quot; alt=&quot;Tests running&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;interacting-with-the-ui&quot;&gt;Interacting with the UI&lt;/h3&gt;
&lt;p&gt;Now that we can find and verify elements, let’s make things interactive.&lt;/p&gt;
&lt;p&gt;TWD integrates directly with &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/testing-library/user-event&quot;&gt;user event&lt;/a&gt; from React Testing Library — a well-known tool that handles realistic user interactions such as clicks, typing, and keyboard input.&lt;/p&gt;
&lt;p&gt;We chose &lt;code&gt;user-event&lt;/code&gt; because it already supports most common browser interactions and mimics how a real user would use the app.&lt;br&gt;
TWD also provides a custom &lt;code&gt;setInputValue&lt;/code&gt; helper for specific inputs that &lt;code&gt;user-event&lt;/code&gt; doesn’t handle perfectly.&lt;/p&gt;
&lt;p&gt;To use it, import both &lt;code&gt;twd&lt;/code&gt; and &lt;code&gt;userEvent&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, userEvent } from &quot;twd-js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, to simulate a click:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const counterButton = await twd.get(&quot;[data-testid='counter-button']&quot;);
await userEvent.click(counterButton.el);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice we pass the element itself (&lt;code&gt;.el&lt;/code&gt;) to &lt;code&gt;userEvent&lt;/code&gt;, as that’s what the library expects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;putting-it-all-together&quot;&gt;Putting It All Together&lt;/h3&gt;
&lt;p&gt;Now let’s combine selectors, assertions, and events into a single test.&lt;br&gt;
Our &lt;code&gt;helloWorld.twd.test.ts&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd, userEvent } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Hello World Page&quot;, () =&amp;gt; {
  it(&quot;should display the welcome title and counter button&quot;, async () =&amp;gt; {
    await twd.visit(&quot;/&quot;);
    
    const title = await twd.get(&quot;[data-testid='welcome-title']&quot;);
    title.should(&quot;be.visible&quot;).should(&quot;have.text&quot;, &quot;Welcome to TWD&quot;);
    
    const counterButton = await twd.get(&quot;[data-testid='counter-button']&quot;);
    counterButton.should(&quot;be.visible&quot;).should(&quot;have.text&quot;, &quot;Count is 0&quot;);
    
    await userEvent.click(counterButton.el);
    counterButton.should(&quot;have.text&quot;, &quot;Count is 1&quot;);
    
    await userEvent.click(counterButton.el);
    counterButton.should(&quot;have.text&quot;, &quot;Count is 2&quot;);
    
    await userEvent.click(counterButton.el);
    counterButton.should(&quot;have.text&quot;, &quot;Count is 3&quot;);
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the test results will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1e29i5r2cuyjsz2mz6l.png&quot; alt=&quot;test running after three clicks&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the UI updates in real time — and since it’s your actual app, you can continue using it normally while your tests run.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next-2&quot;&gt;what's next&lt;/h3&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7407/testing-while-developing-part-4-running-tests-in-ci&quot;&gt;next post&lt;/a&gt;, we’ll explore one of the most powerful features of TWD: network mocking.&lt;br&gt;
This will let developers test their frontend independently, without relying on backend integrations — perfect for building and testing features faster.&lt;/p&gt;
&lt;p&gt;You can also learn more about selectors, assertions, and user events in our &lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/api/twd-commands.html&quot;&gt;official docs&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7371/testing-while-developing-part-2-selectors-assertions-and-user-events</guid>
<pubDate>Fri, 14 Nov 2025 19:06:42 +0000</pubDate>
</item>
<item>
<title>Testing While Developing (Part 1) – Installation and First Test</title>
<link>https://coderlegion.com/7370/testing-while-developing-part-1-installation-and-first-test</link>
<description>&lt;p&gt;Let’s start our journey learning how to use TWD (Test While Developing)!&lt;br&gt;
In this first part, we’ll work with a small finished project that includes two pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Hello World page (perfect for our first test)&lt;/li&gt;
&lt;li&gt;A Todo List page that makes requests to an API powered by JSON Server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our goal in this post is to install TWD and add the sidebar that will host all our tests.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-the-project&quot;&gt;Setting Up the Project&lt;/h3&gt;
&lt;p&gt;We’ll start by setting up our base project. You can clone the repo here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone *Emails are not allowed*:BRIKEV/twd-docs-tutorial.git
cd twd-docs-tutorial
git checkout 01-setup
npm i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This project includes two routes: &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/todos&lt;/code&gt;.&lt;br&gt;
All components and pages are already in place — ready for us to test.&lt;/p&gt;
&lt;p&gt;To run the project locally:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm run serve:dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mzcpj8el0qqkc98bfp9.png&quot; alt=&quot;tutorial homepage&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;getting-started-with-twd&quot;&gt;Getting Started with TWD&lt;/h3&gt;
&lt;p&gt;Now let’s install TWD:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# You can use npm, yarn, or pnpm
npm i --save-dev twd-js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, open &lt;code&gt;src/main.tsx&lt;/code&gt; and add this code snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (import.meta.env.DEV) {
  // You choose how to load the tests; this example uses Vite's glob import
  const testModules = import.meta.glob(&quot;./**/*.twd.test.ts&quot;);
  const { initTests, TWDSidebar } = await import('twd-js');
  // You need to pass the test modules, the sidebar component, and createRoot function
  initTests(testModules, &amp;lt;TWDSidebar open={true} position=&quot;left&quot; /&amp;gt;, createRoot);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;It’s important to include this inside the &lt;code&gt;if (import.meta.env.DEV)&lt;/code&gt; block — this way, TWD won’t be bundled into your production build.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once that’s added, you’ll see the TWD Sidebar, where all your tests will appear:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xokpi55hwh5hrg58t0s.png&quot; alt=&quot;Tutorial homepage with twd sidebar&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;creating-our-first-test&quot;&gt;Creating Our First Test&lt;/h3&gt;
&lt;p&gt;In the snippet above, we’re using this line: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const testModules = import.meta.glob(&quot;./**/*.twd.test.ts&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Vite to automatically load every file matching the pattern &lt;code&gt;./**/*.twd.test.ts&lt;/code&gt;.&lt;br&gt;
So let’s create our very first test file.&lt;/p&gt;
&lt;p&gt;Create a new file at &lt;code&gt;src/twd-tests/helloWorld.twd.test.ts&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Hello World Page&quot;, () =&amp;gt; {
  it(&quot;should display the welcome title and counter button&quot;, async () =&amp;gt; {
    console.log('Executed console.log');
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, your test will appear in the &lt;strong&gt;TWD sidebar&lt;/strong&gt;.&lt;br&gt;
You can click the play icon next to it, or press Run All to execute all tests.&lt;br&gt;
You’ll see the &lt;code&gt;console.log&lt;/code&gt; output in your browser console.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pch7m88wj5dtxwyhnxp.png&quot; alt=&quot;tutorial homepage with tests executed&quot;&gt;&lt;/p&gt;
&lt;p&gt;This test passes (green) because it doesn’t actually test anything yet.&lt;br&gt;
In the next post, we’ll explore assertions and selectors — the real power of TWD.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;bonus-visiting-a-page&quot;&gt;Bonus: Visiting a Page&lt;/h3&gt;
&lt;p&gt;Before we move on, let’s add one small command: &lt;code&gt;twd.visit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Update your test like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { twd } from &quot;twd-js&quot;;
import { describe, it } from &quot;twd-js/runner&quot;;

describe(&quot;Hello World Page&quot;, () =&amp;gt; {
  it(&quot;should display the welcome title and counter button&quot;, async () =&amp;gt; {
    await twd.visit('/');
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command visits the page exactly like Cypress’s cy.visit() — simple and familiar.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/7371/testing-while-developing-part-2-selectors-assertions-and-user-events&quot;&gt;next post&lt;/a&gt;, we’ll dive into assertions and selectors, where you’ll start interacting with elements and verifying real behavior.&lt;/p&gt;
&lt;p&gt;Meanwhile, you can check out the &lt;a rel=&quot;nofollow&quot; href=&quot;https://brikev.github.io/twd/getting-started.html&quot;&gt;TWD documentation&lt;/a&gt;&lt;br&gt;
 for more details.&lt;/p&gt;
</description>
<category>Testing</category>
<guid isPermaLink="true">https://coderlegion.com/7370/testing-while-developing-part-1-installation-and-first-test</guid>
<pubDate>Fri, 14 Nov 2025 19:03:23 +0000</pubDate>
</item>
</channel>
</rss>