<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yobox</title>
    <description>The latest articles on DEV Community by yobox (@yobox).</description>
    <link>https://dev.to/yobox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3981137%2F2f924e03-ddcd-497c-b085-cb7a2dd8dd03.png</url>
      <title>DEV Community: yobox</title>
      <link>https://dev.to/yobox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yobox"/>
    <language>en</language>
    <item>
      <title>How to Pick a Monorepo Tool in 2026</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:26:03 +0000</pubDate>
      <link>https://dev.to/yobox/how-to-pick-a-monorepo-tool-in-2026-kko</link>
      <guid>https://dev.to/yobox/how-to-pick-a-monorepo-tool-in-2026-kko</guid>
      <description>&lt;p&gt;The landscape: Turborepo (caching focus), Nx (plugin ecosystem), pnpm workspaces (minimal, native), Bun workspaces (fast, batteries-included).&lt;/p&gt;

&lt;h1&gt;
  
  
  Quick decision tree
&lt;/h1&gt;

&lt;p&gt;2-3 packages, no CI bottleneck → pnpm workspaces.&lt;br&gt;
Heavy CI, many TS packages → Turborepo.&lt;br&gt;
Polyglot, multiple frameworks → Nx.&lt;br&gt;
Greenfield, Bun-only → Bun workspaces.&lt;br&gt;
Don't over-engineer. Most teams pick Turbo and never touch the cache settings.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why this decision matters more in 2026
&lt;/h1&gt;

&lt;p&gt;A monorepo tool is the foundation under every other build decision you make. It dictates how fast CI runs, how easily new engineers onboard, how cleanly you can extract a package, and how much your release process hurts. Picking wrong is not catastrophic — every tool here can be replaced — but the cost of switching grows linearly with package count and team size. Spend an afternoon on the decision now; you will save a week of migration later.&lt;/p&gt;

&lt;p&gt;The good news: the four serious options in 2026 are all genuinely good. There is no "obvious wrong answer." The bad news: each tool is opinionated, and adopting one without understanding those opinions is how teams end up rewriting their CI six months later.&lt;/p&gt;

&lt;p&gt;The right monorepo tool is the one whose defaults match your team's actual workflow — not the one with the prettiest dashboard.&lt;/p&gt;

&lt;h1&gt;
  
  
  The contenders
&lt;/h1&gt;

&lt;p&gt;Turborepo&lt;br&gt;
Caching-first. Turbo's core value is "never run the same task twice," and it delivers that with a remote cache that works out of the box on Vercel and self-hosts in ~20 lines of config. It stays out of the way for everything else, which is both its strength (low cognitive overhead) and its limit (you bring your own conventions).&lt;/p&gt;

&lt;p&gt;Nx&lt;br&gt;
Plugin-first. Nx ships generators, executors, dependency graphs, and affected-detection for ~30 frameworks. If your monorepo is polyglot (React + NestJS + React Native + Go), Nx gives you a single command surface across all of them. The trade-off is that Nx wants to own your workflow — embrace it and it is delightful, fight it and it is painful.&lt;/p&gt;

&lt;p&gt;pnpm workspaces&lt;br&gt;
Minimal. No task runner, no caching, no graph — just pnpm -r run build and a sane node_modules layout. For 2–10 packages, this is often all you need. Pair it with pnpm --filter ... for selective runs and a homegrown caching layer if CI gets slow.&lt;/p&gt;

&lt;p&gt;Bun workspaces&lt;br&gt;
Fast. Bun's workspace support is solid in 2026, and bun install is dramatically faster than alternatives. The task runner is rudimentary compared to Turbo/Nx, but for greenfield Bun-only projects the integrated story is hard to beat.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison at a glance
&lt;/h1&gt;

&lt;p&gt;Concern Turborepo   Nx  pnpm workspaces Bun workspaces&lt;br&gt;
Setup time  10 min  30 min  2 min   2 min&lt;br&gt;
Remote caching  First-class First-class DIY DIY&lt;br&gt;
Affected detection  Yes Yes (best-in-class) DIY DIY&lt;br&gt;
Generators / scaffolds  None    Many    None    None&lt;br&gt;
Polyglot support    Language-agnostic   Best    Node-only   Node-only&lt;br&gt;
Learning curve  Low High    None    None&lt;br&gt;
Lock-in risk    Low Medium-high None    Low&lt;br&gt;
Best at TS monorepos    Enterprise polyglot Small teams Greenfield speed&lt;br&gt;
Free tool&lt;br&gt;
Try YoBox Temp Mail&lt;br&gt;
Disposable inbox — no signup, instant OTP.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
Lock-in risk is the underrated axis. Turbo and pnpm are easy to leave. Nx generators write code that assumes Nx — leaving means rewriting that code.&lt;/p&gt;

&lt;h1&gt;
  
  
  A decision tree that actually works
&lt;/h1&gt;

&lt;p&gt;Start here&lt;br&gt;
Do you have 2–4 packages and CI under 5 minutes? Use pnpm workspaces. Anything else is over-engineering.&lt;br&gt;
Is the project greenfield, Bun-first, and unlikely to need polyglot support? Use Bun workspaces.&lt;br&gt;
Do you have many TypeScript packages and CI pain? Use Turborepo.&lt;br&gt;
Are you polyglot (multiple frameworks, possibly multiple languages) and willing to adopt opinions? Use Nx.&lt;br&gt;
Tiebreakers&lt;br&gt;
If your team already uses Vercel, Turborepo's remote cache is free and zero-config.&lt;br&gt;
If you ship a design system across many apps, Nx generators reduce the per-app boilerplate dramatically — relevant if you also migrated to Tailwind v4.&lt;br&gt;
If your CI runs Docker builds, pair any choice with the Docker Builder Guide to keep image caches warm.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;p&gt;A two-person startup shipping a React app + a marketing site&lt;br&gt;
pnpm workspaces. One packages/ui shared library, two consumers, no task runner. Adding Turbo here is a solution looking for a problem.&lt;/p&gt;

&lt;p&gt;A 15-engineer company with a React app, a NestJS API, and a shared TS contracts package&lt;br&gt;
Turborepo. The caching pays for itself within a week, and the conventions stay invisible. CI drops from 12 minutes to 3.&lt;/p&gt;

&lt;p&gt;A 60-engineer enterprise with React, React Native, NestJS, Go services, and a design system&lt;br&gt;
Nx. The generators, the dependency graph, and the affected-detection across languages are worth the learning curve. The investment in custom Nx executors becomes a moat.&lt;/p&gt;

&lt;p&gt;A Bun-only side project with three packages&lt;br&gt;
Bun workspaces. bun install in 200 ms, bun run --filter '*' build covers the basics, and you ship faster than you would have spent setting up Turbo.&lt;/p&gt;

&lt;h1&gt;
  
  
  CI patterns that pay off regardless of tool
&lt;/h1&gt;

&lt;p&gt;Cache the package manager store&lt;br&gt;
pnpm store, Bun's global cache, and Nx's node_modules cache all benefit from explicit CI caching keyed on the lockfile hash. This is the single biggest CI win across every tool.&lt;/p&gt;

&lt;p&gt;Run affected, not all&lt;br&gt;
Whether you call it turbo run build --filter=...[HEAD^], nx affected -t build, or a custom git diff script, only building changed packages drops CI time by 60–90% on a healthy monorepo.&lt;/p&gt;

&lt;p&gt;Pin the tool version&lt;br&gt;
packageManager in package.json for pnpm/Bun, and a fixed Turbo/Nx version in devDependencies. Floating versions in a monorepo cause the worst kind of "works on my machine" bug.&lt;/p&gt;

&lt;p&gt;Keep one CI entry point&lt;br&gt;
ci/run.sh that delegates to the tool. When you eventually switch tools, you change one file.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;pnpm workspaces is the right default for small repos.&lt;br&gt;
Turborepo wins for TS-heavy monorepos with CI pain.&lt;br&gt;
Nx wins for polyglot enterprise repos that benefit from generators.&lt;br&gt;
Bun workspaces wins for greenfield, Bun-only projects.&lt;br&gt;
Pin tool versions, cache the store, and always run affected — these matter more than the tool choice.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I mix pnpm workspaces and Turborepo? Yes — that is the most common production setup. pnpm handles installation, Turbo handles task orchestration and caching.&lt;br&gt;
Is Nx overkill for a 10-package repo? For a homogeneous TS repo, yes. For a polyglot repo, no. The line is "do you have more than two frameworks?"&lt;br&gt;
Does Bun replace Turbo? Not yet. Bun is a fast package manager and runtime; Turbo is a task orchestrator with remote caching. They can coexist (packageManager: bun with Turbo on top).&lt;br&gt;
How do I migrate from Lerna? Lerna is effectively legacy in 2026. Most teams move to pnpm workspaces + Turbo. The migration is mechanical: delete lerna.json, add pnpm-workspace.yaml, replace lerna run with turbo run.&lt;br&gt;
What about Rush / Bazel? Rush is solid but losing momentum. Bazel is correct for massive (1000+ package) polyglot monorepos and overkill for everything else.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The 2026 monorepo landscape is healthy enough that you do not need to agonize over the choice. Pick the simplest tool that solves your current pain — pnpm workspaces until CI hurts, Turborepo when it does, Nx when polyglot complexity dominates, Bun when you are starting from scratch and Bun-only. Wrap whatever you pick in a single CI entry point so the next migration is a one-file change, and spend the rest of your energy on the work the monorepo actually contains.&lt;/p&gt;

&lt;h1&gt;
  
  
  Anti-patterns to avoid
&lt;/h1&gt;

&lt;p&gt;Adopting a task runner before you need one A two-package repo with a 30-second build does not need Turbo or Nx. Adding either means onboarding cost, configuration files, and a learning curve that pays back only when CI hurts. Wait for the pain; then solve it.&lt;br&gt;
Pinning everyone to a custom Nx executor Nx executors are powerful but viral — every package that uses one is now bound to the executor's API. Use built-in executors wherever possible, and write custom ones only when the productivity win is undeniable.&lt;br&gt;
Caching without invalidation discipline A remote cache that returns stale outputs is worse than no cache at all. Be explicit about inputs (env vars, file globs, tool versions) for every cached task. The first time CI ships a cached "green" build that should have failed, the team loses trust in the cache forever.&lt;br&gt;
Letting workspace dependencies drift workspace:* is convenient but loose. For published packages, pin to exact versions and bump them with a changesets-style workflow. The discipline pays off the first time you publish a breaking change.&lt;br&gt;
Treating the monorepo as a deployment unit Packages share a repo; they should not share a release cadence. Independent versioning, independent CI pipelines, and independent deploys are the point of the monorepo — collapsing them back into a monolith undoes the value. Combine this discipline with the CI patterns from the Docker Builder Guide and the testing flows in the Cypress and Playwright guides for a release pipeline that scales with the repo.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>turborepo</category>
      <category>nx</category>
      <category>devops</category>
    </item>
    <item>
      <title>Realistic Mock Data for Cypress, Playwright &amp; Postman</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:26:06 +0000</pubDate>
      <link>https://dev.to/yobox/realistic-mock-data-for-cypress-playwright-postman-ioa</link>
      <guid>https://dev.to/yobox/realistic-mock-data-for-cypress-playwright-postman-ioa</guid>
      <description>&lt;p&gt;The cheapest test failure to debug is the one that says exactly what's wrong. The most expensive one is the test that passes against "&lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;" and "John Doe" and then explodes the moment a real user with an apostrophe in their name signs up. Realistic mock data is the difference.&lt;/p&gt;

&lt;p&gt;This guide covers a complete mock-data toolkit for Cypress, Playwright, and Postman — emails from YoBox, passwords from the Password Generator, filler text from Lorem Ipsum, and a few patterns for the long tail of edge cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why "&lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;" is a problem
&lt;/h1&gt;

&lt;p&gt;"&lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;" has been registered as a real account on most major SaaS apps at least once a day for the last decade. Your CI is competing with everyone else's. Worse, your tests probably pass because the response matches expectations — even when the database update silently failed because a unique constraint fired.&lt;/p&gt;

&lt;p&gt;The fix is one HTTP call:&lt;/p&gt;

&lt;p&gt;const inbox = await fetch("&lt;a href="https://yobox.dev/api/mail/new" rel="noopener noreferrer"&gt;https://yobox.dev/api/mail/new&lt;/a&gt;", { method: "POST" }).then(r =&amp;gt; r.json());&lt;br&gt;
Now every test gets an address no one else owns, ever.&lt;/p&gt;

&lt;p&gt;The four categories of mock data&lt;br&gt;
Category    Source  Edge cases to cover&lt;br&gt;
Emails  YoBox Temp Mail Plus addressing, long local part&lt;br&gt;
Passwords   Password Generator  Unicode, length 64+&lt;br&gt;
Names   Faker / custom  Apostrophes, CJK, RTL&lt;br&gt;
Long text   Lorem Ipsum Newlines, emoji, HTML entities&lt;br&gt;
The first two are completely solved by YoBox. The other two need a faker library plus deliberate edge cases.&lt;/p&gt;
&lt;h1&gt;
  
  
  A reusable factory module
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/factories.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@faker-js/faker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeUser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;inboxId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;password&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;memorable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;T!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;edgeCaseUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;O'Brien&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Müller&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;李&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;明&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ñoño&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;García&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Anne-Marie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;de la Vega&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That one file covers 95% of the variety you need.&lt;/p&gt;
&lt;h1&gt;
  
  
  Cypress
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;makeUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;edgeCaseUsers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../factories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Signup variety&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;happy path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeUser&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=email]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;edgeCaseUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handles&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;${u.firstName} ${u.lastName}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeUser&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=first]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=last]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=email]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Playwright
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fixtures&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;makeUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./factories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variety&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;makeUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Postman
&lt;/h1&gt;

&lt;p&gt;In a pre-request script:&lt;/p&gt;

&lt;p&gt;const first = pm.variables.replaceIn("{{$randomFirstName}}");&lt;br&gt;
const last = pm.variables.replaceIn("{{$randomLastName}}");&lt;br&gt;
pm.collectionVariables.set("firstName", first);&lt;br&gt;
pm.collectionVariables.set("lastName", last);&lt;br&gt;
Pair with the YoBox inbox bootstrap from the Postman + YoBox guide.&lt;/p&gt;

&lt;p&gt;Long-form text&lt;br&gt;
For description fields, comment bodies, blog posts:&lt;/p&gt;

&lt;p&gt;const longText = await fetch("/* lorem source */").then(r =&amp;gt; r.text());&lt;br&gt;
Or simply paste blocks from YoBox Lorem Ipsum into a fixtures file. Include at least one block with emoji, one with newlines, and one with HTML entities — they catch the most regressions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Payload fixtures
&lt;/h1&gt;

&lt;p&gt;Free tool&lt;br&gt;
Open Cypress Guide&lt;br&gt;
End-to-end recipes for Cypress + YoBox.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
Don't write JSON by hand:&lt;/p&gt;

&lt;p&gt;export const invoicePayload = (overrides = {}) =&amp;gt; ({&lt;br&gt;
  id: crypto.randomUUID(),&lt;br&gt;
  customer_email: "",&lt;br&gt;
  amount_cents: 4200,&lt;br&gt;
  currency: "USD",&lt;br&gt;
  created_at: new Date().toISOString(),&lt;br&gt;
  ...overrides,&lt;br&gt;
});&lt;br&gt;
Override the fields a test actually cares about; leave the rest as realistic defaults.&lt;/p&gt;
&lt;h1&gt;
  
  
  Edge cases worth always testing
&lt;/h1&gt;

&lt;p&gt;Email with +tag addressing.&lt;br&gt;
Name with a single apostrophe.&lt;br&gt;
Name with a hyphen.&lt;br&gt;
256-character display name.&lt;br&gt;
Empty middle name vs missing middle name ("" vs undefined).&lt;br&gt;
Currency with thousands separator.&lt;br&gt;
Timestamps in non-UTC zones.&lt;/p&gt;
&lt;h1&gt;
  
  
  Pairs with
&lt;/h1&gt;

&lt;p&gt;YoBox Temp Mail for unique inboxes.&lt;br&gt;
Password Generator for credentials.&lt;br&gt;
Lorem Ipsum for body copy.&lt;br&gt;
Regex Patterns cheat sheet for validation assertions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;Hard-coded "&lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;" — every issue traceable to this string is your fault.&lt;br&gt;
No CJK or RTL coverage — your i18n layer is untested.&lt;br&gt;
Predictable timestamps — new Date(0) everywhere will hide timezone bugs.&lt;br&gt;
Reusing the same fake user — defeats the purpose of fixtures.&lt;/p&gt;
&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Should I commit fixtures to git?&lt;br&gt;
Static fixtures yes; generated user data no.&lt;/p&gt;

&lt;p&gt;Is Faker deterministic?&lt;br&gt;
It can be, via faker.seed(123). Use seeded data for snapshot tests, random for everything else.&lt;/p&gt;

&lt;p&gt;How big should a fixture file be?&lt;br&gt;
Small. If it's over 200 lines, you're testing the fixture, not the app.&lt;/p&gt;

&lt;p&gt;What about PII?&lt;br&gt;
Never use real customer data, even anonymized. Faker + YoBox covers every legitimate need.&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Realistic mock data is the cheapest quality investment your team can make. YoBox gives you the inboxes and the passwords; Faker plus a small edge-case list covers names and payloads; Lorem Ipsum covers prose. Wire it all into a single factories module and every test in Cypress, Playwright, and Postman gets variety for free.&lt;/p&gt;

&lt;p&gt;See also: Stop Using Fake Data in Production Demos, Cypress + YoBox, Secure Test Credentials.&lt;/p&gt;
&lt;h1&gt;
  
  
  Locales and i18n
&lt;/h1&gt;

&lt;p&gt;Faker supports locale-specific data. Set \faker.locale = "ja"\ for Japanese names and addresses, then run your suite in that locale to catch text-direction and character-width bugs.&lt;/p&gt;
&lt;h1&gt;
  
  
  Payment data
&lt;/h1&gt;

&lt;p&gt;Use Stripe's published test card numbers (\4242 4242 4242 4242\ for success, \4000 0000 0000 0002\ for decline) — never generate fake card numbers, even for tests. Real card-number formats can trigger fraud rules in scanners.&lt;/p&gt;
&lt;h1&gt;
  
  
  Date and timezone variety
&lt;/h1&gt;

&lt;p&gt;Generate timestamps across at least three timezones in every fixture set. The bugs around DST, leap seconds, and ISO formatting are not edge cases — they are weekly cases in any global app.&lt;/p&gt;

&lt;p&gt;Migration from fixed fixtures&lt;br&gt;
Replace \fixture("user.json")\ calls with factory calls one folder at a time. Each PR is small, reviewable, and instantly increases coverage of the messy edges that fixed fixtures never exercise.&lt;/p&gt;
&lt;h1&gt;
  
  
  A data generation strategy that survives contact with reality
&lt;/h1&gt;

&lt;p&gt;Mock data should be realistic, deterministic, and disposable. Realistic so you catch shape bugs; deterministic so failures reproduce; disposable so a leak is a non-event.&lt;/p&gt;

&lt;p&gt;The standard stack:&lt;/p&gt;

&lt;p&gt;Faker for shapes (names, addresses, lorem ipsum).&lt;br&gt;
A seeded RNG so two runs of the same test produce the same data.&lt;br&gt;
YoBox primitives for things Faker can't produce — real inboxes, real webhook URLs, real cryptographic strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@faker-js/faker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;qa&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;}@&lt;/span&gt;&lt;span class="nd"&gt;yobox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lorem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sentences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Cypress fixtures vs. factories
&lt;/h1&gt;

&lt;p&gt;Fixtures (cypress/fixtures/user.json) are great for static reference data. The moment a value must differ per run — an email, a username, a timestamp — switch to a factory function called from your test.&lt;/p&gt;

&lt;p&gt;// cypress/support/factories.ts&lt;br&gt;
export const newUser = () =&amp;gt; ({&lt;br&gt;
  email: &lt;code&gt;qa+${Date.now()}@yobox.dev&lt;/code&gt;,&lt;br&gt;
  password: crypto.randomUUID(),&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  Playwright + storage state
&lt;/h1&gt;

&lt;p&gt;Playwright's storageState lets you log in once and reuse the session across tests. Pair this with a factory that registers a fresh user during globalSetup so every CI run gets a clean account.&lt;/p&gt;

&lt;p&gt;// playwright/global-setup.ts&lt;br&gt;
import { chromium } from "@playwright/test";&lt;br&gt;
export default async function () {&lt;br&gt;
  const browser = await chromium.launch();&lt;br&gt;
  const page = await browser.newPage();&lt;br&gt;
  // sign up using YoBox temp mail for the OTP&lt;br&gt;
  await page.goto("/signup");&lt;br&gt;
  // ...&lt;br&gt;
  await page.context().storageState({ path: "state.json" });&lt;br&gt;
  await browser.close();&lt;br&gt;
}&lt;/p&gt;

&lt;h1&gt;
  
  
  Postman: dynamic variables done right
&lt;/h1&gt;

&lt;p&gt;Postman ships dozens of {{$random...}} variables. The trap is that they regenerate on every reference, so chaining requests with the same value requires capturing once:&lt;/p&gt;

&lt;p&gt;pm.collectionVariables.set("email", &lt;code&gt;qa+${pm.variables.replaceIn("{{$timestamp}}")}@yobox.dev&lt;/code&gt;);&lt;/p&gt;

&lt;h1&gt;
  
  
  Realistic vs. random
&lt;/h1&gt;

&lt;p&gt;Random data is not realistic data. asdf qwer will pass most validators and miss every visual bug. Faker's locale-aware generators (faker.location.streetAddress({ useFullAddress: true })) catch UI overflows that random strings will not.&lt;/p&gt;

&lt;p&gt;Generator   Realism Determinism Use when&lt;br&gt;
Faker (seeded)  High    Yes Default for E2E suites&lt;br&gt;
Random ASCII    None    Easy    Property tests, fuzzing&lt;br&gt;
Production-derived dumps    Highest Yes Never (PII risk)&lt;br&gt;
YoBox Temp Mail address Real    Per-run Anything that sends real email&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;Tests pass with mock data, fail with real users.&lt;br&gt;
Your mock data is too uniform. Inject edge cases: names with apostrophes, addresses without zip codes, emoji in bios.&lt;/p&gt;

&lt;p&gt;Faker output changes between versions.&lt;br&gt;
Pin Faker as an exact dependency in CI, or your snapshot tests will explode after a routine upgrade.&lt;/p&gt;

&lt;p&gt;Postman variables behave unpredictably across requests.&lt;br&gt;
Use pm.collectionVariables for per-run values and pm.variables only for true throwaways.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Should I share fixtures across Cypress and Playwright?&lt;br&gt;
If both suites cover the same flows, yes. Extract a fixtures/ package and consume from both. Otherwise you'll drift.&lt;/p&gt;

&lt;p&gt;How do I avoid PII in test data?&lt;br&gt;
Generate everything. Never copy from production. If you must mirror production shape, anonymize at the dump stage, not at the test stage.&lt;/p&gt;

&lt;p&gt;What about contract tests?&lt;br&gt;
Pact or Postman's schema tests cover contracts; mock data covers behavior. They're complementary, not competing.&lt;/p&gt;

&lt;p&gt;Where does YoBox fit?&lt;br&gt;
Temp Mail replaces hand-rolled inbox stubs. The Webhook Tester replaces requestbin-style listeners. The Password Generator replaces Password123!. Pull each in as you need it instead of building local equivalents.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison: fixture files vs. factories vs. live faker
&lt;/h1&gt;

&lt;p&gt;Approach    Reproducibility Edge-case coverage  Refactor cost   Best for&lt;br&gt;
Static JSON fixtures    Perfect Low — only what you typed High — touch every file   Snapshot tests, contract tests&lt;br&gt;
Seeded factories    Perfect (with a seed)   High — randomized within rules    Low — change the factory  Most E2E and integration tests&lt;br&gt;
Live faker (unseeded)   None    Maximum Low Exploratory and fuzz testing&lt;br&gt;
Fixed fixtures lie to you. They pass on Monday because nothing changed since Friday, not because the code is correct. Factories with a seeded RNG give you reproducibility and breadth at the same time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;p&gt;Cypress signup flows with Temp Mail&lt;br&gt;
Combine a seeded factory with a fresh YoBox inbox per spec. The factory mints a realistic name, address, and locale; YoBox mints a real email address that actually receives the verification message. The Cypress + YoBox guide shows the full custom-command setup.&lt;/p&gt;

&lt;p&gt;Playwright parallel-safe fixtures&lt;br&gt;
When Playwright runs eight workers in parallel, every worker needs a unique user. A factory keyed off test.info().workerIndex guarantees uniqueness without coordination. See the Playwright + YoBox guide for the worker-scoped fixture pattern.&lt;/p&gt;

&lt;p&gt;Postman collections shared across teams&lt;br&gt;
A Postman collection that hardcodes { "email": "&lt;a href="mailto:alice@example.com"&gt;alice@example.com&lt;/a&gt;" } breaks the moment two engineers run it against the same environment. Replace the body with {{$randomEmail}} or, better, a pre-request script that calls into a factory function. The Postman + YoBox guide covers the YoBox-backed assertion side.&lt;/p&gt;

&lt;p&gt;CI seed data for staging&lt;br&gt;
The same factory that generates test data in Cypress can seed staging databases. One module, two callers — staging looks like production, and your QA team stops asking for "more realistic data."&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Treat test data as code: version it, review it, and refactor it.&lt;br&gt;
Seed your RNG so failures are reproducible without sacrificing coverage.&lt;br&gt;
Use real domains (example.com) or YoBox-issued inboxes for email fields — never &lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;.&lt;br&gt;
Generate one realistic value per attribute rather than copy/pasting the same string twelve times.&lt;br&gt;
Ship a factory module on day one; retrofitting it after a hundred specs is painful.&lt;br&gt;
The bugs your tests miss live in the data your tests never generate. Realistic mock data is the cheapest coverage you can buy.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>cypress</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Secure Test Credentials with the YoBox Password Generator</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:44:34 +0000</pubDate>
      <link>https://dev.to/yobox/secure-test-credentials-with-the-yobox-password-generator-40g7</link>
      <guid>https://dev.to/yobox/secure-test-credentials-with-the-yobox-password-generator-40g7</guid>
      <description>&lt;p&gt;Test credentials are the most under-thought attack surface in modern engineering. Every team has a password: "Password123!" somewhere in a seed file or a Cypress fixture. Every team's CI logs probably contain that string. And every team is one accidentally-public S3 bucket away from an embarrassing incident report.&lt;/p&gt;

&lt;p&gt;The YoBox Password Generator is built for exactly this problem: cryptographically strong, configurable, and easy to wire into any test suite. This article shows the patterns we recommend for credentials in Cypress, Playwright, Postman, and seed scripts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why "Password123!" is worse than you think
&lt;/h1&gt;

&lt;p&gt;It's not just weak — it's fingerprinted. Public credential dumps include it. Bot scanners try it first. Compliance auditors flag it on sight. Even if your test environment is firewalled, the habit leaks: developers paste the same string into the dev environment, then staging, then "just this once" into production.&lt;/p&gt;

&lt;p&gt;A unique password per fixture costs nothing and removes the entire class of incident.&lt;/p&gt;

&lt;h1&gt;
  
  
  The pattern
&lt;/h1&gt;

&lt;p&gt;Generate a strong password at runtime, never check it in.&lt;/p&gt;

&lt;p&gt;// helpers/credentials.ts&lt;br&gt;
export const newPassword = () =&amp;gt; {&lt;br&gt;
  const charset = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&amp;amp;*";&lt;br&gt;
  const buf = crypto.getRandomValues(new Uint8Array(20));&lt;br&gt;
  return Array.from(buf, (b) =&amp;gt; charset[b % charset.length]).join("");&lt;br&gt;
};&lt;br&gt;
In a browser/test environment, crypto.getRandomValues is the right primitive. Don't reach for Math.random — it's not cryptographically secure and any auditor will flag it. See Generating Cryptographically Secure Passwords in the Browser for the deep dive.&lt;/p&gt;
&lt;h1&gt;
  
  
  Cypress
&lt;/h1&gt;

&lt;p&gt;Cypress.Commands.add("newCredentials", () =&amp;gt;&lt;br&gt;
  cy.task("newInbox").then((inbox) =&amp;gt; ({&lt;br&gt;
    email: inbox.address,&lt;br&gt;
    inboxId: inbox.id,&lt;br&gt;
    password: &lt;code&gt;Test!${Date.now()}-${Math.random().toString(36).slice(2, 10)}&lt;/code&gt;,&lt;br&gt;
  }))&lt;br&gt;
);&lt;br&gt;
For tests that just need a credential and don't care about replay, that's enough. For tests that will be audited (SOC 2 evidence, pen-test runs), generate via crypto instead.&lt;/p&gt;
&lt;h1&gt;
  
  
  Playwright
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateStrongPassword&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Postman / Newman
&lt;/h1&gt;

&lt;p&gt;In a pre-request script:&lt;/p&gt;

&lt;p&gt;const charset = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&amp;amp;*";&lt;br&gt;
const arr = new Uint8Array(20);&lt;br&gt;
crypto.getRandomValues(arr);&lt;br&gt;
const password = Array.from(arr, (b) =&amp;gt; charset[b % charset.length]).join("");&lt;br&gt;
pm.collectionVariables.set("password", password);&lt;/p&gt;
&lt;h1&gt;
  
  
  Seed scripts
&lt;/h1&gt;

&lt;p&gt;The most common leak vector. Generate seed passwords at seed time, print the bcrypt hash to the DB, and emit the plaintext to a one-time-use file outside the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mkdirSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;carol&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateStrongPassword&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hashSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.secrets/dev-passwords.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;t$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add .secrets to .gitignore. Print the file path; never echo passwords to stdout in CI.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring strength
&lt;/h1&gt;

&lt;p&gt;The Password Generator UI exposes the same knobs you should expose in your helpers:&lt;/p&gt;

&lt;p&gt;Knob    Test default    Production default&lt;br&gt;
Length  20  24+&lt;br&gt;
Symbols Yes Yes&lt;br&gt;
Ambiguous chars Excluded    Excluded&lt;br&gt;
Numbers Yes Yes&lt;br&gt;
Uppercase   Yes Yes&lt;br&gt;
Excluding ambiguous characters (0/O, 1/l/I) prevents the worst class of "I typed it wrong from the screenshot" bugs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pairs with
&lt;/h1&gt;

&lt;p&gt;YoBox Temp Mail for fresh test emails.&lt;br&gt;
Cypress + YoBox for end-to-end signup flows.&lt;br&gt;
Regex Patterns Every QA Engineer Should Memorize for validating password complexity assertions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;Math.random() in security-adjacent code — never. Always crypto.getRandomValues.&lt;br&gt;
Committing seed plaintext — keep it in .gitignored files.&lt;br&gt;
Same password across environments — generate per environment, per run.&lt;br&gt;
Echoing to CI logs — mask in GHA with ::add-mask::.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Free tool&lt;br&gt;
Generate Secure Password&lt;br&gt;
Cryptographically strong, fully client-side.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
How long should test passwords be?&lt;br&gt;
20 characters with mixed casing, numbers, and symbols. Long enough that brute-forcing is moot, short enough to type in a debugger.&lt;/p&gt;

&lt;p&gt;Should I rotate test credentials?&lt;br&gt;
Yes — per CI run, per local dev session. The whole point is throwaway.&lt;/p&gt;

&lt;p&gt;What about service account credentials?&lt;br&gt;
Different problem — use your secret manager (GitHub Actions secrets, AWS Secrets Manager). The password generator is for human test accounts.&lt;/p&gt;

&lt;p&gt;Is the generator deterministic?&lt;br&gt;
No, and it shouldn't be. Determinism would defeat the purpose.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Strong, unique, throwaway credentials cost nothing and eliminate the entire category of "leaked test password" incidents. The YoBox Password Generator gives you the UI; the patterns above wire it into Cypress, Playwright, Postman, and your seed scripts so your test data is as serious as your production data.&lt;/p&gt;

&lt;p&gt;See also: Generating Cryptographically Secure Passwords in the Browser, Cypress + YoBox, Realistic Mock Data.&lt;/p&gt;

&lt;h1&gt;
  
  
  Rotation strategies
&lt;/h1&gt;

&lt;p&gt;Per-run rotation is the default. For shared seed users that need to survive across days, rotate weekly via a scheduled CI job that regenerates the password and updates the secret store.&lt;/p&gt;

&lt;p&gt;رVault integration&lt;br&gt;
Pipe generated passwords directly into HashiCorp Vault, AWS Secrets Manager, or 1Password CLI so they're never persisted in plain text on a developer's machine.&lt;/p&gt;

&lt;p&gt;\\bash&lt;br&gt;
op item create --category=login --title="QA-bot" \&lt;br&gt;
--vault=QA password="$(yobox-genpass 24)"&lt;br&gt;
\\&lt;/p&gt;

&lt;h1&gt;
  
  
  Audit trails
&lt;/h1&gt;

&lt;p&gt;For SOC 2 evidence, log that a password was generated and when, never what. The generator runs entirely client-side, so the audit trail lives in your CI logs or your secret store's access history.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration from hardcoded fixtures
&lt;/h1&gt;

&lt;p&gt;Grep for any string matching common weak-password regexes and replace with a runtime call to the generator. A one-day refactor that removes an entire category of audit findings.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why test credentials deserve real entropy
&lt;/h1&gt;

&lt;p&gt;Most teams treat test credentials as throwaway strings and end up reusing the same five passwords across staging, CI, and local dev. That works right up until the day a staging dump leaks, an intern pushes a .env.test to a public repo, or a screen recording captures Password123! on a login form during a demo. Treating every credential — even "fake" ones — as if it could escape into the wild is the only sustainable habit.&lt;/p&gt;

&lt;p&gt;The YoBox Password Generator produces high-entropy strings on the client using crypto.getRandomValues. Nothing is sent to a server, nothing is logged, and nothing is cached. That property matters for compliance-adjacent teams who need a defensible answer to "where did this password come from?"&lt;/p&gt;

&lt;h1&gt;
  
  
  Practical use cases
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Seeding ephemeral users in E2E tests
Pair the generator with Cypress or Playwright. Generate one credential per test run, store it in a closure (never in a fixture file), and discard it when the suite finishes.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright/fixtures/user.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&amp;amp;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qa&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}@&lt;/span&gt;&lt;span class="nd"&gt;yobox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Bootstrapping local dev databases
When a developer joins the team, the onboarding script should generate a per-machine database password instead of shipping a default. Pipe the output of the generator into your .env.local template:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;yobox-pass --length 32 --symbols &amp;gt;&amp;gt; .env.local&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CI secret rotation drills
Quarterly rotation drills feel theoretical until you actually do one. Use the generator to produce candidate values, store them in your secret manager, redeploy, and verify nothing breaks. The drill itself is the deliverable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Length, character classes, and entropy
&lt;/h1&gt;

&lt;p&gt;Length  Character set   Approx. entropy Use case&lt;br&gt;
12  letters + digits    ~71 bits    Throwaway test logins&lt;br&gt;
16  letters + digits + !    ~104 bits   Shared dev environments&lt;br&gt;
24  full symbols    ~157 bits   Service accounts&lt;br&gt;
32  full symbols    ~210 bits   Root / break-glass&lt;br&gt;
Anything below 70 bits is fine for transient test accounts that exist for one CI run. Anything that survives the run should be 100+ bits.&lt;/p&gt;

&lt;h1&gt;
  
  
  Storing test credentials safely
&lt;/h1&gt;

&lt;p&gt;Never commit .env files, even ones suffixed with .test or .example if they contain real values.&lt;br&gt;
Use your CI provider's encrypted secrets (GitHub Actions encrypted secrets, GitLab CI variables, Render environment groups).&lt;br&gt;
Rotate every credential that appears in a screen-share, demo recording, or screenshot — assume it is compromised.&lt;br&gt;
For E2E suites, prefer generated-per-run credentials over static fixture users. Static users invite bypass code that ships to production.&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;The generated password is rejected by my signup form.&lt;br&gt;
Most forms enforce a regex like ^(?=.[A-Z])(?=.\d)(?=.*[!@#$]). Generate with mixed character classes enabled and re-test.&lt;/p&gt;

&lt;p&gt;Special characters break my shell.&lt;br&gt;
Wrap the value in single quotes or pipe through base64 for transport. Better: read the value from stdin in your tooling so the shell never sees it.&lt;/p&gt;

&lt;p&gt;My password manager refuses to autofill.&lt;br&gt;
This usually means the form sets autocomplete="off" or uses a non-standard input type. Use the manager's manual fill, then report the form to the vendor.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is the generator safe for production secrets?&lt;br&gt;
It is cryptographically sound, yes — but production secrets should be generated and stored inside your secret manager (AWS Secrets Manager, Doppler, 1Password CLI) so rotation and audit logging are first-class. The generator is ideal for ad-hoc and test use.&lt;/p&gt;

&lt;p&gt;Does YoBox log generated passwords?&lt;br&gt;
No. Generation happens entirely in the browser. The page never makes a network request when you click Generate. You can verify this in DevTools → Network.&lt;/p&gt;

&lt;p&gt;Can I script the generator?&lt;br&gt;
The web UI is the official surface. For scripting, mirror the algorithm with a one-liner using crypto.getRandomValues in Node 19+ or the equivalent in your language of choice.&lt;/p&gt;

&lt;p&gt;How does it compare to other generators?&lt;br&gt;
Tool    Client-side No tracking Symbol control  Free&lt;br&gt;
YoBox Password  Yes Yes Yes Yes&lt;br&gt;
1Password   Yes Yes Yes Paid&lt;br&gt;
Random "password" sites Sometimes   Often no    Limited Yes&lt;br&gt;
Where do I plug this into my workflow?&lt;br&gt;
Pair it with the Webhook Tester for webhook signing secrets, with Temp Mail for end-to-end signup tests, and with the Regex Assistant when you need to validate that your generated values match your application's password policy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison: credential strategies for test suites
&lt;/h1&gt;

&lt;p&gt;Strategy    Entropy CI-safe Audit-friendly  Maintenance&lt;br&gt;
Hardcoded Password123!  ~30 bits    No  No  Zero, until the audit&lt;br&gt;
Shared .env file    Variable    Risky — leaks via logs    No  Manual rotation&lt;br&gt;
Per-run generated via YoBox Password Generator  128+ bits   Yes Yes None&lt;br&gt;
Vault-backed dynamic secrets    128+ bits   Yes Yes (best)  Vault setup&lt;br&gt;
A test credential is a production credential that hasn't leaked yet. Treat both with the same generation pipeline and you remove an entire class of incidents from your roadmap.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;p&gt;Cypress per-spec users&lt;br&gt;
Mint a unique 24-character password at the top of each spec, sign up a new user against a YoBox inbox, then run the flow. Nothing persists, nothing shares state, and parallel workers never collide. Wire-up details live in the Cypress + YoBox guide.&lt;/p&gt;

&lt;p&gt;Playwright global setup&lt;br&gt;
For suites that prefer a stable authenticated state, generate one strong password during globalSetup, store it via storageState, and replay it across every worker. The Playwright + YoBox guide shows the parallel-safe pattern, and the credential itself comes from the same generator covered in Generating Cryptographically Secure Passwords in the Browser.&lt;/p&gt;

&lt;p&gt;Postman / Newman environments&lt;br&gt;
Use a pre-request script to call the generator, set pm.environment.set("password", value), then reference {{password}} in subsequent requests. The credential never lands in the collection JSON, so the file remains safe to commit. The Postman + YoBox guide covers the assertion side.&lt;/p&gt;

&lt;p&gt;Docker-based CI matrices&lt;br&gt;
When your CI runs against the Docker Builder Guide stack, mint credentials inside the runner container and export them as environment variables for the test process only. Containers are ephemeral; credentials die with them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Generate fresh credentials per CI run by default; promote to shared only when a test genuinely requires persistence.&lt;br&gt;
Use the YoBox Password Generator or crypto.getRandomValues directly — never Math.random, never a hand-typed string.&lt;br&gt;
Combine generated credentials with disposable Temp Mail addresses to keep the entire test identity ephemeral.&lt;br&gt;
Validate complexity assertions with patterns from the Regex Patterns cheat sheet.&lt;br&gt;
Log that a credential was generated, never what — your audit trail belongs in CI metadata, not stdout.&lt;br&gt;
Static test credentials are the password equivalent of TODO: remove before launch. They never get removed, and they always show up in the breach report.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Cypress E2E with YoBox: Disposable Email + Webhook Tester</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sat, 20 Jun 2026 17:10:37 +0000</pubDate>
      <link>https://dev.to/yobox/cypress-e2e-with-yobox-disposable-email-webhook-tester-hoj</link>
      <guid>https://dev.to/yobox/cypress-e2e-with-yobox-disposable-email-webhook-tester-hoj</guid>
      <description>&lt;p&gt;Learn how to test signup flows, OTP verification, password resets, and webhook events end-to-end using Cypress with disposable email inboxes and real-time webhook inspection. &lt;/p&gt;

&lt;p&gt;The two flows every SaaS app has, and the two flows almost every Cypress suite stubs out, are signup with email verification and outbound webhooks. Stubbing those is convenient. It's also exactly why your QA team finds bugs in production that the suite missed. YoBox gives Cypress a real disposable inbox and a real webhook receiver — both over plain HTTP — so you can test the full round-trip without ngrok, without a shared mailbox, and without a single environment variable that needs rotating.&lt;/p&gt;

&lt;p&gt;This walkthrough builds a complete e2e suite from scratch.&lt;/p&gt;

&lt;h1&gt;
  
  
  What you'll build
&lt;/h1&gt;

&lt;p&gt;By the end of this article you'll have:&lt;/p&gt;

&lt;p&gt;A Cypress project wired to the YoBox Temp Mail and Webhook Tester endpoints.&lt;br&gt;
A signup → OTP → onboarding test that runs against your real backend.&lt;br&gt;
A billing → webhook test that asserts payload shape, not just delivery.&lt;br&gt;
A CI configuration that runs four shards in parallel with zero shared state.&lt;/p&gt;
&lt;h1&gt;
  
  
  Install and wire up
&lt;/h1&gt;

&lt;p&gt;npm i -D cypress cypress-wait-until&lt;br&gt;
cypress.config.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;e2e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="nf"&gt;setupNodeEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;newInbox&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;readInbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/messages&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;newHook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;readHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cypress/support/e2e.js:&lt;/p&gt;

&lt;p&gt;import "cypress-wait-until";&lt;/p&gt;

&lt;h1&gt;
  
  
  Test 1 — Signup with email OTP
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verifies a brand-new user with OTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newInbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=email]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=password]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;StrongPass!42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=submit]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readInbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readInbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=otp]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test creates a never-before-used email, walks the signup form, polls for the OTP, types it, and asserts the redirect. No mocks. No shared inbox. Safe to run in parallel.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test 2 — Webhook delivery
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Billing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delivers invoice.paid to the partner webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newHook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/integrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[data-test=callback]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send test invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readHook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readHook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount_cents&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second expect — payload shape — is the assertion that actually prevents regressions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test 3 — Magic-link auth
&lt;/h1&gt;

&lt;p&gt;The pattern generalizes. Magic links are just OTPs that happen to be URLs.&lt;/p&gt;

&lt;p&gt;cy.task("readInbox", inbox.id).then((d) =&amp;gt; {&lt;br&gt;
  const url = d.messages[0].text.match(/https?:\/\/\S+/)[0];&lt;br&gt;
  cy.visit(url);&lt;br&gt;
  cy.url().should("include", "/dashboard");&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison: stubbed vs YoBox
&lt;/h1&gt;

&lt;p&gt;Aspect  Stubbed email   YoBox disposable inbox&lt;br&gt;
Catches SMTP regressions    No  Yes&lt;br&gt;
Tests real template No  Yes&lt;br&gt;
Parallel-safe   Yes (trivially) Yes&lt;br&gt;
CI setup    None    One env var&lt;br&gt;
Production fidelity Low High&lt;br&gt;
Free tool&lt;br&gt;
Open Cypress Guide&lt;br&gt;
End-to-end recipes for Cypress + YoBox.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
The same table applies to webhook stubs vs the Webhook Tester.&lt;/p&gt;

&lt;h1&gt;
  
  
  CI
&lt;/h1&gt;

&lt;p&gt;jobs:&lt;br&gt;
  cypress:&lt;br&gt;
    strategy:&lt;br&gt;
      matrix: { shard: [1, 2, 3, 4] }&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: cypress-io/github-action@v6&lt;br&gt;
        with:&lt;br&gt;
          parallel: true&lt;br&gt;
          record: true&lt;br&gt;
          group: e2e-${{ matrix.shard }}&lt;br&gt;
        env:&lt;br&gt;
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}&lt;br&gt;
          YOBOX: &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt;&lt;br&gt;
Add the Docker Builder recipe if you want fully containerized runs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Helpers worth promoting to your support folder
&lt;/h1&gt;

&lt;p&gt;`&lt;code&gt;js&lt;br&gt;
// cypress/support/commands.js&lt;br&gt;
Cypress.Commands.add("newUser", () =&amp;gt;&lt;br&gt;
cy.task("newInbox").then((inbox) =&amp;gt; ({&lt;br&gt;
email: inbox.address,&lt;br&gt;
inboxId: inbox.id,&lt;br&gt;
password: Test!${Date.now()}&lt;/code&gt;,&lt;br&gt;
}))&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Cypress.Commands.add("readOtp", (inboxId) =&amp;gt;&lt;br&gt;
cy.task("readInbox", inboxId).then((d) =&amp;gt; d.messages[0].text.match(/\b\d{6}\b/)[0])&lt;br&gt;
);&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;javascript&lt;/p&gt;

&lt;p&gt;Now any spec reads like English:&lt;/p&gt;

&lt;p&gt;cy.newUser().then((u) =&amp;gt; {&lt;br&gt;
  cy.visit("/signup");&lt;br&gt;
  cy.get("[data-test=email]").type(u.email);&lt;br&gt;
  // ...&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  Pairs nicely with
&lt;/h1&gt;

&lt;p&gt;Password Generator for strong test credentials.&lt;br&gt;
Regex Assistant for tuning extraction patterns.&lt;br&gt;
Lorem Ipsum for realistic form fixtures.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;cy.wait(5000). Use cy.waitUntil against the YoBox endpoint — fail fast on real signal.&lt;br&gt;
Regex that catches the year. Anchor OTP regex with \b\d{6}\b.&lt;br&gt;
Forgetting to assert webhook shape. Count &amp;gt; 0 is a smoke test; shape assertion is the regression test.&lt;br&gt;
Re-using an inbox across tests. Each test should ask for its own — it's cheap and avoids cross-test leakage.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Does this work with cy.session?&lt;br&gt;
Yes — cache the verified session per test user. Each user still gets their own YoBox inbox.&lt;/p&gt;

&lt;p&gt;Can I download attachments?&lt;br&gt;
Yes; the messages endpoint exposes attachment metadata.&lt;/p&gt;

&lt;p&gt;What about non-ASCII email bodies?&lt;br&gt;
The plain-text part is UTF-8; text.match(...) handles emoji and CJK fine.&lt;/p&gt;

&lt;p&gt;Do I need a paid plan for parallelization?&lt;br&gt;
YoBox is free. Cypress's Dashboard parallelization needs a record key.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;YoBox turns Cypress into a test runner that can honestly exercise every flow your users actually hit — signup, OTP, magic links, billing, partner webhooks — without stubs, without ngrok, and without shared state. Wire up the four tasks, write two specs, and you're already covering ground most QA suites never reach.&lt;/p&gt;

&lt;p&gt;Further reading: The Complete Cypress + YoBox Guide, Playwright Automation with YoBox, and Realistic Mock Data.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: combining inbox + webhook in one test
&lt;/h1&gt;

&lt;p&gt;Some flows depend on both — a signup that sends an email and notifies a partner via webhook. Provision both resources in \beforeEach\ and assert both at the end.&lt;/p&gt;

&lt;p&gt;\\js&lt;br&gt;
beforeEach(() =&amp;gt; {&lt;br&gt;
cy.task("newInbox").as("inbox");&lt;br&gt;
cy.task("newHook").as("hook");&lt;br&gt;
});&lt;br&gt;
\\&lt;/p&gt;

&lt;h1&gt;
  
  
  Retries and flake control
&lt;/h1&gt;

&lt;p&gt;\retries: { runMode: 1, openMode: 0 }\ in \cypress.config.js\ absorbs single-request network blips in CI without masking real bugs locally. Combined with YoBox's high-availability polling endpoint, this brings practical flake rate to under 0.5%.&lt;/p&gt;

&lt;h1&gt;
  
  
  Long-running integration tests
&lt;/h1&gt;

&lt;p&gt;For tests that span minutes — invoice cycles, scheduled jobs — increase the YoBox poll timeout to 5 minutes and add a progress log every 30 seconds so CI doesn't appear hung.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration playbook
&lt;/h1&gt;

&lt;p&gt;Replace the shared QA inbox with a per-test YoBox inbox in one PR. Replace the staging webhook URL with a YoBox hook in the next. Each PR is a clean refactor with a tiny diff and an immediately visible flakiness drop.&lt;/p&gt;

&lt;h1&gt;
  
  
  A complete Cypress + YoBox setup
&lt;/h1&gt;

&lt;p&gt;The pattern below is what we ship in production for E2E flows that touch real email and real webhooks.&lt;/p&gt;

&lt;p&gt;cypress.config.ts&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`ts&lt;br&gt;
import { defineConfig } from "cypress";&lt;/p&gt;

&lt;p&gt;export default defineConfig({&lt;br&gt;
e2e: {&lt;br&gt;
baseUrl: process.env.BASE_URL ?? "&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;",&lt;br&gt;
setupNodeEvents(on) {&lt;br&gt;
on("task", {&lt;br&gt;
async "yobox:newInbox"() {&lt;br&gt;
const r = await fetch("&lt;a href="https://yobox.dev/api/mail/new" rel="noopener noreferrer"&gt;https://yobox.dev/api/mail/new&lt;/a&gt;", { method: "POST" });&lt;br&gt;
return await r.json(); // { address, token }&lt;br&gt;
},&lt;br&gt;
async "yobox:pollOtp"(token: string) {&lt;br&gt;
const deadline = Date.now() + 30_000;&lt;br&gt;
while (Date.now() &amp;lt; deadline) {&lt;br&gt;
const m = await fetch(&lt;a href="https://yobox.dev/api/mail/$%7Btoken%7D/latest).then(r" rel="noopener noreferrer"&gt;https://yobox.dev/api/mail/${token}/latest).then(r&lt;/a&gt; =&amp;gt; r.json());&lt;br&gt;
const code = m?.text?.match(/\b\d{6}\b/)?.[0];&lt;br&gt;
if (code) return code;&lt;br&gt;
await new Promise(r =&amp;gt; setTimeout(r, 1000));&lt;br&gt;
}&lt;br&gt;
throw new Error("OTP timeout");&lt;br&gt;
},&lt;br&gt;
"yobox:newHook"() {&lt;br&gt;
const id = crypto.randomUUID();&lt;br&gt;
return { id, url: &lt;a href="https://yobox.dev/api/hooks/$%7Bid%7D" rel="noopener noreferrer"&gt;https://yobox.dev/api/hooks/${id}&lt;/a&gt; };&lt;br&gt;
},&lt;br&gt;
async "yobox:pollHook"(id: string) {&lt;br&gt;
const r = await fetch(&lt;a href="https://yobox.dev/api/hooks/$%7Bid%7D).then(r" rel="noopener noreferrer"&gt;https://yobox.dev/api/hooks/${id}).then(r&lt;/a&gt; =&amp;gt; r.json());&lt;br&gt;
return r.requests ?? [];&lt;br&gt;
},&lt;br&gt;
});&lt;br&gt;
},&lt;br&gt;
},&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Custom commands&lt;br&gt;
// cypress/support/commands.ts&lt;br&gt;
Cypress.Commands.add("waitForHook", (id: string, ms = 15000) =&amp;gt; {&lt;br&gt;
  const start = Date.now();&lt;br&gt;
  const check = (): any =&amp;gt; cy.task("yobox:pollHook", id).then((reqs: any[]) =&amp;gt; {&lt;br&gt;
    if (reqs.length) return reqs[0];&lt;br&gt;
    if (Date.now() - start &amp;gt; ms) throw new Error("Webhook timeout");&lt;br&gt;
    return cy.wait(500).then(check);&lt;br&gt;
  });&lt;br&gt;
  return check();&lt;br&gt;
});&lt;br&gt;
Signup spec&lt;br&gt;
describe("signup with real OTP", () =&amp;gt; {&lt;br&gt;
  it("verifies a generated code", () =&amp;gt; {&lt;br&gt;
    cy.task("yobox:newInbox").then((inbox: any) =&amp;gt; {&lt;br&gt;
      cy.visit("/signup");&lt;br&gt;
      cy.get("[name=email]").type(inbox.address);&lt;br&gt;
      cy.contains("button", /send code/i).click();&lt;br&gt;
      cy.task("yobox:pollOtp", inbox.token).then((code: string) =&amp;gt; {&lt;br&gt;
        cy.get("[name=otp]").type(code);&lt;br&gt;
        cy.contains("button", /continue/i).click();&lt;br&gt;
      });&lt;br&gt;
      cy.contains(/welcome/i).should("be.visible");&lt;br&gt;
    });&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
Webhook spec&lt;br&gt;
describe("integration webhook", () =&amp;gt; {&lt;br&gt;
  it("delivers a payload to a YoBox URL", () =&amp;gt; {&lt;br&gt;
    cy.task("yobox:newHook").then((hook: any) =&amp;gt; {&lt;br&gt;
      cy.visit("/integrations/new");&lt;br&gt;
      cy.get("[name=webhookUrl]").type(hook.url);&lt;br&gt;
      cy.contains("button", /save/i).click();&lt;br&gt;
      cy.contains("button", /send test event/i).click();&lt;br&gt;
      cy.waitForHook(hook.id).then((req: any) =&amp;gt; {&lt;br&gt;
        expect(req.method).to.eq("POST");&lt;br&gt;
        const body = JSON.parse(req.body);&lt;br&gt;
        expect(body.event).to.eq("integration.test");&lt;br&gt;
      });&lt;br&gt;
    });&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  CI/CD
&lt;/h1&gt;

&lt;p&gt;Run Cypress in GitHub Actions with the official action and split across workers:&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  cypress:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    strategy:&lt;br&gt;
      matrix: { containers: [1, 2, 3, 4] }&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: cypress-io/github-action@v6&lt;br&gt;
        with:&lt;br&gt;
          parallel: true&lt;br&gt;
          record: true&lt;br&gt;
          group: "PR-${{ github.event.pull_request.number }}"&lt;br&gt;
        env:&lt;br&gt;
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}&lt;br&gt;
For dockerized runs, see the Docker builder for Cypress and Playwright CI.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cypress vs. Playwright for this workflow
&lt;/h1&gt;

&lt;p&gt;Concern Cypress Playwright&lt;br&gt;
Polling external API mid-test   cy.task Direct fetch&lt;br&gt;
Multi-tab signup → admin approval Workaround  Native&lt;br&gt;
Time-travel debugging   Yes (best in class) Trace viewer&lt;br&gt;
Parallel runs   Dashboard / OSS sharding    Built-in&lt;br&gt;
Onboarding curve    Lower   Slightly higher&lt;br&gt;
If your team already invests in the Cypress dashboard, stick with Cypress. If you're starting fresh and need multi-context flows, Playwright wins.&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;cy.task returns undefined.&lt;br&gt;
Tasks must return non-undefined values. Wrap polling helpers so they return null instead of undefined on miss.&lt;/p&gt;

&lt;p&gt;OTP test passes locally, flakes in CI.&lt;br&gt;
Increase the OTP polling deadline (CI mail delivery is slower) and ensure parallel workers each create their own inbox.&lt;/p&gt;

&lt;p&gt;Webhook test flakes.&lt;br&gt;
You're either reusing a hook ID across runs or your app isn't waiting for save confirmation before firing the test event. Always assert "saved" before triggering.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I use YoBox without cy.task?&lt;br&gt;
Yes — cy.request works for the same endpoints, but cy.task keeps secrets and polling logic out of the browser context, which is cleaner.&lt;/p&gt;

&lt;p&gt;How do I retry the OTP poll only?&lt;br&gt;
Wrap the task call in Cypress.Promise and recurse with a max-attempts counter, or move the loop into the Node task as shown above.&lt;/p&gt;

&lt;p&gt;Does YoBox bill per inbox?&lt;br&gt;
No. The disposable inbox and webhook endpoints are free for normal CI usage. If you have unusually high volume, ping us.&lt;/p&gt;

&lt;p&gt;Where can I see real-world usage?&lt;br&gt;
See the companion guides for Playwright, Postman, and realistic mock data.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>testing</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Playwright + YoBox.dv: Parallel-Safe End-to-End Testing (2026)</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Fri, 19 Jun 2026 17:44:57 +0000</pubDate>
      <link>https://dev.to/yobox/playwright-yoboxdv-parallel-safe-end-to-end-testing-2026-2lje</link>
      <guid>https://dev.to/yobox/playwright-yoboxdv-parallel-safe-end-to-end-testing-2026-2lje</guid>
      <description>&lt;p&gt;Playwright won the modern e2e race by treating parallelism as a first-class feature instead of an afterthought. Workers are isolated, fixtures are scoped, and traces tell you exactly what happened on the slowest CI box at 2 AM. The catch: the moment your test needs a real email or a real webhook receiver, that beautifully isolated worker model collapses unless the supporting infrastructure is also isolated.&lt;/p&gt;

&lt;p&gt;YoBox closes the gap. Every test gets its own disposable inbox and its own webhook URL, both reachable over plain HTTP, both fully isolated. This guide shows the patterns we recommend for production Playwright suites in 2026.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why YoBox fits Playwright
&lt;/h1&gt;

&lt;p&gt;Playwright workers run in parallel by default. Two workers sharing a mailbox is a race condition with a stopwatch. YoBox gives each worker — and each test — its own:&lt;/p&gt;

&lt;p&gt;Disposable email address, provisioned with a single POST.&lt;br&gt;
Webhook capture URL with a JSON readback API.&lt;br&gt;
Zero auth, zero rate-limit dance, zero SDK.&lt;br&gt;
That maps cleanly onto Playwright's fixture model.&lt;/p&gt;
&lt;h1&gt;
  
  
  Project setup
&lt;/h1&gt;

&lt;p&gt;npm init playwright@latest&lt;br&gt;
Add a typed YoBox client and a pair of fixtures in tests/fixtures.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Inbox&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;waitForEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/messages&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;waitForHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Webhook timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration layer. Every spec from here on is just a normal Playwright test that happens to receive an inbox and a hook.&lt;/p&gt;

&lt;h1&gt;
  
  
  Signup + OTP
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;waitForEmail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fixtures&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user can sign up with OTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sup3rSecret!2026&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verification code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;welcome/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test is honest: a real email arrives, a real OTP gets typed, a real redirect happens. No stubs, no shared state, safe to run 32-wide.&lt;/p&gt;

&lt;h1&gt;
  
  
  Webhook assertions
&lt;/h1&gt;

&lt;p&gt;Pair the inbox fixture with the hook fixture and you can verify the outbound side of any integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid fires the partner webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/integrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Webhook URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send test event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Webhook Tester UI mirrors the same data, so a human can replay any failed CI run in the browser.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sharding in CI
&lt;/h1&gt;

&lt;p&gt;Playwright's --shard flag splits specs across machines. YoBox makes that safe because no resource is shared:&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  e2e:&lt;br&gt;
    strategy:&lt;br&gt;
      matrix: { shard: [1/4, 2/4, 3/4, 4/4] }&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - run: npm ci &amp;amp;&amp;amp; npx playwright install --with-deps&lt;br&gt;
      - run: npx playwright test --shard=${{ matrix.shard }}&lt;br&gt;
        env:&lt;br&gt;
          YOBOX: &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt;&lt;br&gt;
Four shards, four times faster, zero collisions.&lt;/p&gt;
&lt;h1&gt;
  
  
  Traces, screenshots, and email bodies
&lt;/h1&gt;

&lt;p&gt;When a flaky test fails in CI, Playwright traces tell you what the browser did. They don't show what arrived in the inbox. Attach the email body to the trace so debugging is one click:&lt;/p&gt;

&lt;p&gt;Free tool&lt;br&gt;
Open Playwright Guide&lt;br&gt;
Parallel-safe E2E with Playwright.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastEmail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// already attached by waitForEmail helper&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or push the body via testInfo.attach("inbox.txt", { body: msg.text, contentType: "text/plain" }) inside the waiter.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison: Playwright vs Cypress with YoBox
&lt;/h1&gt;

&lt;p&gt;Capability  Playwright + YoBox  Cypress + YoBox&lt;br&gt;
Native parallel workers Yes Requires cloud&lt;br&gt;
TypeScript fixtures First-class Plugin-based&lt;br&gt;
Multi-tab / multi-context   Yes Limited&lt;br&gt;
Time-travel debugger    Trace viewer    Built-in&lt;br&gt;
Disposable email    YoBox   YoBox&lt;br&gt;
Webhook capture YoBox   YoBox&lt;br&gt;
Both are excellent. Pick Playwright when you need cross-browser, multi-context, or maximum parallelism. Pick Cypress when DX and the time-travel debugger matter most to your team.&lt;/p&gt;
&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;Re-using fixtures across tests. Default scope is per-test, which is what you want. Don't promote them to worker unless you understand the implications.&lt;br&gt;
await page.waitForTimeout. Use waitForEmail / waitForHook polls instead — they fail fast on real signal.&lt;br&gt;
Hard-coded OTPs. Parse from the email every time, even in "happy path" tests. It's free insurance against template changes.&lt;br&gt;
No retries on the YoBox call. A single network blip shouldn't fail your suite. Wrap fetches with one retry.&lt;/p&gt;
&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I run YoBox locally for offline development?&lt;br&gt;
Yes — the same HTTP API is available on yobox.dev. For air-gapped CI, mock the fixtures.&lt;/p&gt;

&lt;p&gt;Does the inbox support HTML emails?&lt;br&gt;
It captures both. Parse text for assertions; render html only when you need a visual snapshot.&lt;/p&gt;

&lt;p&gt;How do I test rate-limited flows?&lt;br&gt;
Use Playwright's test.describe.serial for that one file; everything else stays parallel.&lt;/p&gt;

&lt;p&gt;Can I use this with auth tokens?&lt;br&gt;
Yes — store tokens in a fixture scoped to worker and rotate per spec when needed.&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Playwright handles the browser. YoBox handles the inbox and the webhook receiver. Wire them through two fixtures and you have a test suite that scales horizontally without a single shared resource. Add traces, attach the email bodies, and your future self will thank you the next time CI goes red at 4 AM.&lt;/p&gt;

&lt;p&gt;Further reading: Cypress + YoBox, Postman + YoBox, and Docker Builder for CI.&lt;/p&gt;
&lt;h1&gt;
  
  
  Advanced: project-level configuration
&lt;/h1&gt;

&lt;p&gt;Playwright projects let you run the same tests under different conditions — desktop Chromium, mobile WebKit, slow 3G. Pair that with YoBox by reading a single base URL from env and letting every project share the same inbox/hook fixtures.&lt;/p&gt;

&lt;p&gt;\\ts&lt;br&gt;
export default defineConfig({&lt;br&gt;
projects: [&lt;br&gt;
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },&lt;br&gt;
{ name: "mobile-safari", use: { ...devices["iPhone 14"] } },&lt;br&gt;
],&lt;br&gt;
use: { baseURL: process.env.BASE_URL, trace: "on-first-retry" },&lt;br&gt;
});&lt;br&gt;
\\&lt;/p&gt;
&lt;h1&gt;
  
  
  Advanced: storage state for parallel speedups
&lt;/h1&gt;

&lt;p&gt;For suites that re-authenticate constantly, save storage state from a one-time setup project and reuse it across the workers. Each authenticated session gets its own YoBox inbox so password reset and email-change flows stay isolated.&lt;/p&gt;
&lt;h1&gt;
  
  
  Migration from Cypress
&lt;/h1&gt;

&lt;p&gt;If you're moving from Cypress, the YoBox layer ports unchanged. The fixture syntax is different but the HTTP calls are identical. Most teams migrate one folder at a time and run both runners in CI during the transition.&lt;/p&gt;
&lt;h1&gt;
  
  
  Observability
&lt;/h1&gt;

&lt;p&gt;Attach inbox transcripts and webhook payloads to failed runs so the trace viewer shows the full conversation. The pattern adds about ten lines per fixture and saves hours of "what did the email actually contain?" debugging.&lt;/p&gt;
&lt;h1&gt;
  
  
  A production-grade Playwright config
&lt;/h1&gt;

&lt;p&gt;The two-line fixture above is enough to start, but a real project benefits from a config that pins parallelism, retries, projects, and reporters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;fullyParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;forbidOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;junit.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]],&lt;/span&gt;
&lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;only-on-failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retain-on-failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Desktop Safari&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Desktop Firefox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pixel 7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four browsers, four workers, two retries on CI. This is the sweet spot for most teams.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fixtures, expanded
&lt;/h1&gt;

&lt;p&gt;The fixture pattern in the intro provisions an inbox per test. Real suites usually want a webhook URL and a credential at the same time — wrap them into a single yobox fixture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/fixtures/yobox.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;yobox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;inboxRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hookRes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api/mail/new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api/hooks/new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;inboxRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hookRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789!@#$%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now any spec that imports test from this file gets a fresh inbox, a fresh webhook URL, and a fresh credential — all in parallel, all isolated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Polling patterns
&lt;/h1&gt;

&lt;p&gt;expect.poll is the single most underused API in Playwright. It replaces await new Promise(r =&amp;gt; setTimeout(r, 5000)) everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fixtures/yobox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;magic link signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yobox&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yobox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sign up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//yobox.dev/api/mail/${yobox.inbox.id}/messages);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/\S&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intervals array gives exponential-ish backoff for free.&lt;/p&gt;

&lt;h1&gt;
  
  
  CI shape
&lt;/h1&gt;

&lt;h1&gt;
  
  
  .github/workflows/playwright.yml
&lt;/h1&gt;

&lt;p&gt;jobs:&lt;br&gt;
  e2e:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    strategy:&lt;br&gt;
      fail-fast: false&lt;br&gt;
      matrix:&lt;br&gt;
        shard: ["1/4", "2/4", "3/4", "4/4"]&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: actions/setup-node@v4&lt;br&gt;
        with: { node-version: 20 }&lt;br&gt;
      - run: npm ci&lt;br&gt;
      - run: npx playwright install --with-deps&lt;br&gt;
      - run: npx playwright test --shard=${{ matrix.shard }}&lt;br&gt;
Four shards × four workers = 16-way parallelism, and YoBox's per-test isolation means none of them collide.&lt;/p&gt;

&lt;h1&gt;
  
  
  Playwright vs. Cypress
&lt;/h1&gt;

&lt;p&gt;Concern Playwright  Cypress&lt;br&gt;
True parallelism    Yes, in-process workers Per-machine shards&lt;br&gt;
Multi-tab / multi-origin    First-class Limited&lt;br&gt;
API ergonomics  Async/await everywhere  Chainable, retry built-in&lt;br&gt;
Trace viewer    World-class Time-travel debugger&lt;br&gt;
YoBox integration   Test fixtures   cy.task wrappers&lt;br&gt;
Best for    Cross-domain, multi-context flows   App-centric flows on one origin&lt;br&gt;
Read the companion Cypress guide for the mirrored pattern.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Cross-domain SSO flow&lt;br&gt;
User clicks "Sign in with Google", lands on Google's mock, returns to your app. Playwright handles the cross-origin navigation natively; Cypress fights it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Webhook + UI assertion in one test&lt;br&gt;
Trigger an action that fires a webhook, capture the payload via Webhook Tester, and assert the resulting UI state — all in one fixture-isolated spec.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mobile + desktop matrix&lt;br&gt;
The projects array runs the same spec across desktop Chrome, mobile Pixel, and Safari. Catches viewport regressions before designers notice them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-user invitations&lt;br&gt;
Two inboxes per test (extend the fixture), invite user B from user A's session, assert delivery and acceptance. The webkit project will sometimes catch cookie/storage issues Chromium happily ignores.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Playwright's parallelism is real. Make every dependent resource — inboxes, webhooks, credentials — per-test, or the parallelism becomes flakiness.&lt;/p&gt;

&lt;p&gt;Wrap inbox + webhook + password into one yobox fixture.&lt;br&gt;
Use expect.poll instead of arbitrary sleeps.&lt;br&gt;
Shard in CI; combine with workers for double parallelism.&lt;br&gt;
Trace on first retry, video on failure — costs nothing, saves hours.&lt;br&gt;
Validate OTP/token regex in the RegEx Assistant.&lt;br&gt;
Pin everything in a Docker image for reproducibility.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Should I use Playwright or Cypress?&lt;br&gt;
If your app touches multiple origins, run Playwright. If your app is a single SPA on one origin and your team is small, either works — go with the one your engineers know.&lt;/p&gt;

&lt;p&gt;How do I keep webkit and firefox stable?&lt;br&gt;
Run them on PR, not on every push. They're slower and catch a different class of bug. Reserve them for merge gates.&lt;/p&gt;

&lt;p&gt;What about visual regression?&lt;br&gt;
Playwright's toHaveScreenshot is built in. Combine it with YoBox-provided deterministic data so screenshots don't churn.&lt;/p&gt;

&lt;p&gt;How does this pair with Postman?&lt;br&gt;
Postman covers API contracts; Playwright covers user flows. See the Postman guide for the API-side workflow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Playwright is the right default for new test suites in 2026 — true parallelism, real cross-origin support, and a fixture model that composes cleanly with external services. Wire it to YoBox Temp Mail, the Webhook Tester, and the Password Generator, put the whole thing inside a pinned Docker image, and you have a CI harness that scales from "one engineer on a laptop" to "fifty engineers across four time zones" without changing shape.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>automation</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Testing APIs with Postman + YoBox: The 2026 Workflow</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Thu, 18 Jun 2026 14:32:01 +0000</pubDate>
      <link>https://dev.to/yobox/testing-apis-with-postman-yobox-the-2026-workflow-26ae</link>
      <guid>https://dev.to/yobox/testing-apis-with-postman-yobox-the-2026-workflow-26ae</guid>
      <description>&lt;p&gt;Postman is the lingua franca of API testing. Every backend team has a collection. Most of those collections quietly skip the parts of the API that depend on email delivery or outbound webhooks, because Postman alone can't verify either. That's the gap YoBox fills — a disposable inbox and a webhook receiver, both reachable over plain HTTP, both perfect for pre-request scripts and tests.&lt;/p&gt;

&lt;p&gt;This guide shows the patterns we recommend for serious Postman + YoBox workflows in 2026, from a first request to a Newman pipeline running in CI.&lt;/p&gt;

&lt;h1&gt;
  
  
  The mental model
&lt;/h1&gt;

&lt;p&gt;YoBox exposes three things Postman cares about:&lt;/p&gt;

&lt;p&gt;POST /api/mail/new → returns { id, address }.&lt;br&gt;
GET /api/mail/:id/messages → returns received emails.&lt;br&gt;
POST /api/hooks/new → returns { id, url }, and GET /api/hooks/:id returns captured requests.&lt;br&gt;
Store the IDs in collection variables, poll in pre-request scripts, assert in the Tests tab. That's the whole pattern.&lt;/p&gt;
&lt;h1&gt;
  
  
  Collection variables
&lt;/h1&gt;

&lt;p&gt;Create three: yoboxBase, inboxId, hookId. Set yoboxBase to &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt; in your environment so you can swap to a self-hosted instance later.&lt;/p&gt;
&lt;h1&gt;
  
  
  Provisioning an inbox
&lt;/h1&gt;

&lt;p&gt;POST {{yoboxBase}}/mail/new&lt;br&gt;
Tests tab:&lt;/p&gt;

&lt;p&gt;const body = pm.response.json();&lt;br&gt;
pm.collectionVariables.set("inboxId", body.id);&lt;br&gt;
pm.collectionVariables.set("inboxAddress", body.address);&lt;br&gt;
pm.test("inbox created", () =&amp;gt; pm.expect(body.address).to.include("@"));&lt;/p&gt;
&lt;h1&gt;
  
  
  Triggering a signup
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST {{apiBase}}/auth/signup
Content-Type: application/json

{
"email": "{{inboxAddress}}",
"password": "Sup3rSecret!2026"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Polling for the OTP in a pre-request script
&lt;/h1&gt;

&lt;p&gt;Pre-request scripts can do real async work — perfect for waiting on email delivery before the verification request fires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inboxId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yoboxBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/messages, &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;err, res&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; =&amp;gt; &lt;/span&gt;&lt;span class="err"&gt;{
&lt;/span&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OTP timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Verifying the OTP
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST {{apiBase}}/auth/verify
Content-Type: application/json

{ "email": "{{inboxAddress}}", "otp": "{{otp}}" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests tab:&lt;/p&gt;

&lt;p&gt;pm.test("verification succeeded", () =&amp;gt; pm.response.to.have.status(200));&lt;br&gt;
pm.test("returns session token", () =&amp;gt; pm.expect(pm.response.json().token).to.be.a("string"));&lt;/p&gt;
&lt;h1&gt;
  
  
  Webhook assertions
&lt;/h1&gt;

&lt;p&gt;The Webhook Tester gives you a unique URL per test run. Postman registers it as the callback, fires the trigger, then asserts delivery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;### 1. Create webhook
POST {{yoboxBase}}/hooks/new

// Tests
const b = pm.response.json();
pm.collectionVariables.set("hookId", b.id);
pm.collectionVariables.set("hookUrl", b.url);

2. Trigger POST {{apiBase}}/events Content-Type: application/json
{ "type": "invoice.paid", "callback": "{{hookUrl}}" }

3. Assert delivery GET {{yoboxBase}}/hooks/{{hookId}}
// Tests
const d = pm.response.json();
pm.test("webhook fired", () =&amp;gt; pm.expect(d.count).to.be.above(0));
pm.test("payload shape", () =&amp;gt; {
const body = JSON.parse(d.requests[0].body);
pm.expect(body.event).to.eql("invoice.paid");
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Running in CI with Newman
&lt;/h1&gt;

&lt;p&gt;npx newman run collection.json -e env.json --reporters cli,junit&lt;br&gt;
GitHub Actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run: npx newman run collection.json -e env.json
env:
YOBOX_BASE: &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt;
Newman runs pre-request scripts the same way Postman does, so the OTP poll above works unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Patterns worth stealing
&lt;/h1&gt;

&lt;p&gt;Pattern Why&lt;br&gt;
One inbox per folder    Folder-scoped pre-request keeps tests isolated.&lt;br&gt;
Hook per request    Avoids leaking captures across unrelated assertions.&lt;br&gt;
Env-driven base URL Staging vs production swap with no script changes.&lt;br&gt;
Always parse text/  HTML emails change; plain text stays stable.&lt;/p&gt;

&lt;h1&gt;
  
  
  Security and credentials
&lt;/h1&gt;

&lt;p&gt;Free tool&lt;br&gt;
Open Postman Guide&lt;br&gt;
API workflows with Postman + YoBox.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
Don't paste production API keys into a shared Postman workspace. Use environments with secret variables, and pair test users with the YoBox Password Generator so every collection run uses fresh credentials. For payload sanity checks, Regex Assistant is a faster scratchpad than Postman's snippets.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;Forgetting the await. Pre-request scripts run async — pm.sendRequest is callback-based, wrap it in a Promise.&lt;br&gt;
OTP regex too loose. \d{4,8} will match phone numbers and timestamps. Anchor with \b\d{6}\b.&lt;br&gt;
Hard-coded inbox addresses. Always fetch a new one per run.&lt;br&gt;
Skipping the webhook assert. A 200 from your own API isn't proof the partner got the event.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Does this work in the Postman cloud runner?&lt;br&gt;
Yes — pre-request scripts and pm.sendRequest work identically.&lt;/p&gt;

&lt;p&gt;Can I assert against email headers?&lt;br&gt;
Yes — the messages endpoint exposes from, to, subject, and headers.&lt;/p&gt;

&lt;p&gt;How do I clean up?&lt;br&gt;
YoBox inboxes auto-expire; no cleanup call needed. For tight loops, reuse the same inbox within a folder.&lt;/p&gt;

&lt;p&gt;What about gRPC or GraphQL APIs?&lt;br&gt;
Postman supports both; the YoBox plumbing is identical because it's just HTTP.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Postman is great at firing requests and asserting responses. It's not great at waiting on side effects — email and webhooks live in that exact blind spot. YoBox plugs into pre-request scripts and the Tests tab with nothing but pm.sendRequest and a handful of collection variables. Wire it up once and your Newman pipeline can verify the full round trip of every flow that touches an inbox or an outbound webhook.&lt;/p&gt;

&lt;p&gt;Related: Cypress + YoBox, Playwright + YoBox, Realistic Mock Data.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: chained collections with monitors
&lt;/h1&gt;

&lt;p&gt;Postman Monitors run collections on a schedule. Pair a monitor with the YoBox webhook endpoint to verify that production keeps delivering — not just that staging worked the day you shipped.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: data-driven runs
&lt;/h1&gt;

&lt;p&gt;Newman's --iteration-data\ flag runs the same collection N times against a CSV of inputs. Generate a fresh YoBox inbox per iteration in the pre-request script so each run is fully isolated.&lt;/p&gt;

&lt;p&gt;\\js&lt;br&gt;
const r = await new Promise((res, rej) =&amp;gt;&lt;br&gt;
pm.sendRequest({ url: pm.collectionVariables.get("yoboxBase") + "/mail/new", method: "POST" },&lt;br&gt;
(err, x) =&amp;gt; err ? rej(err) : res(x.json())));&lt;br&gt;
pm.iterationData.set("email", r.address);&lt;br&gt;
\\&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration from manual QA
&lt;/h1&gt;

&lt;p&gt;Most teams start with Postman as a manual tool and gradually formalize it into automation. The bridge is the Tests tab: every assertion you add today is a regression test tomorrow. YoBox makes the side-effect assertions cheap enough to add liberally.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reporting
&lt;/h1&gt;

&lt;p&gt;Newman's HTML reporters surface YoBox-backed assertions exactly like any other test, so QA dashboards already know how to render them. No new tooling required.&lt;/p&gt;

&lt;h1&gt;
  
  
  Beyond the basics
&lt;/h1&gt;

&lt;p&gt;The first time you open Postman, you make a request, you see a response, you close the tab. The second time, you realize it can be a test runner, a documentation tool, a mock server, and a CI artifact. This section is for that second visit.&lt;/p&gt;

&lt;p&gt;Collections as code&lt;br&gt;
Treat collection.json like source. Commit it. Review changes in PRs. Diff it. Postman's JSON format is verbose but stable enough to review.&lt;/p&gt;

&lt;p&gt;postman/&lt;br&gt;
  collection.json&lt;br&gt;
  env.local.json&lt;br&gt;
  env.staging.json&lt;br&gt;
  env.ci.json     # safe placeholders, real values injected at runtime&lt;br&gt;
  README.md&lt;br&gt;
Scripts that scale&lt;br&gt;
Pre-request and test scripts run in a sandboxed JavaScript environment. Two patterns pay for themselves immediately:&lt;/p&gt;

&lt;p&gt;// Pre-request: refresh auth token if expired&lt;br&gt;
const exp = pm.environment.get("tokenExp");&lt;br&gt;
if (!exp || Date.now() &amp;gt; Number(exp) - 30000) {&lt;br&gt;
  pm.sendRequest({&lt;br&gt;
    url: pm.environment.get("authUrl"),&lt;br&gt;
    method: "POST",&lt;br&gt;
    body: { mode: "raw", raw: JSON.stringify({ client_id: pm.environment.get("clientId") }) },&lt;br&gt;
    header: { "Content-Type": "application/json" },&lt;br&gt;
  }, (_, res) =&amp;gt; {&lt;br&gt;
    const j = res.json();&lt;br&gt;
    pm.environment.set("token", j.access_token);&lt;br&gt;
    pm.environment.set("tokenExp", String(Date.now() + j.expires_in * 1000));&lt;br&gt;
  });&lt;br&gt;
}&lt;br&gt;
// Test: assert response shape, not values&lt;br&gt;
pm.test("response shape", () =&amp;gt; {&lt;br&gt;
  const body = pm.response.json();&lt;br&gt;
  pm.expect(body).to.have.all.keys("id", "email", "createdAt");&lt;br&gt;
  pm.expect(body.id).to.match(/^[0-9a-f-]{36}$/);&lt;br&gt;
});&lt;br&gt;
Integrating with YoBox&lt;br&gt;
For any flow that touches email — signup, password reset, magic links — replace fixture emails with YoBox Temp Mail addresses. For anything async, replace local listeners with a YoBox Webhook Tester URL captured into a collection variable.&lt;/p&gt;

&lt;p&gt;// In a "create webhook subscription" request's Tests tab:&lt;br&gt;
const hookId = crypto.randomUUID();&lt;br&gt;
pm.collectionVariables.set("hookUrl", &lt;code&gt;https://yobox.dev/api/hooks/${hookId}&lt;/code&gt;);&lt;br&gt;
pm.collectionVariables.set("hookId", hookId);&lt;br&gt;
Newman, parallelism, and reporting&lt;br&gt;
Newman runs collections from the command line. For CI, the two flags worth knowing:&lt;/p&gt;

&lt;p&gt;npx newman run collection.json \&lt;br&gt;
  -e env.ci.json \&lt;br&gt;
  --reporters cli,junit,htmlextra \&lt;br&gt;
  --reporter-junit-export junit.xml \&lt;br&gt;
  --reporter-htmlextra-export report.html \&lt;br&gt;
  --bail folder&lt;br&gt;
--bail folder stops the run when a folder fails, preserving the rest of the report for triage.&lt;/p&gt;

&lt;h1&gt;
  
  
  Postman vs. the field
&lt;/h1&gt;

&lt;p&gt;Capability  Postman Insomnia    Bruno   Hurl&lt;br&gt;
GUI workflow    Yes Yes Yes No&lt;br&gt;
Plain-text storage  No  Partial Yes Yes&lt;br&gt;
CLI runner  Newman  inso    bru hurl&lt;br&gt;
Pre/post scripts    JS  JS  JS  No&lt;br&gt;
Mock server Cloud   No  No  No&lt;br&gt;
Async webhook polling   Manual  Manual  Manual  Manual&lt;br&gt;
Pairs with YoBox Temp Mail  Yes Yes Yes Yes&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;"Could not get any response."&lt;br&gt;
Cert error, DNS issue, or the request is firewalled. Check Console (View → Show Postman Console) for the underlying error.&lt;/p&gt;

&lt;p&gt;Variable not interpolated.&lt;br&gt;
Wrong scope. Global &amp;gt; collection &amp;gt; environment &amp;gt; local. The most specific scope wins; the most specific empty value also wins.&lt;/p&gt;

&lt;p&gt;Newman exit code 1 with no failed tests.&lt;br&gt;
A script threw. Look at the JSON reporter output — run.failures will include script errors as well as assertion failures.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is Postman free for teams?&lt;br&gt;
The desktop app is free for collections, environments, and Newman. Cloud collaboration features are tiered. For small teams, exporting + git is sufficient.&lt;/p&gt;

&lt;p&gt;How do I version a collection?&lt;br&gt;
Commit collection.json and tag releases. Postman's built-in versioning is cloud-only and harder to review.&lt;/p&gt;

&lt;p&gt;Can Postman test gRPC and WebSockets?&lt;br&gt;
Yes — both are first-class in recent versions. The same script/test model applies.&lt;/p&gt;

&lt;p&gt;How does this fit with Cypress and Playwright?&lt;br&gt;
Postman covers the API contract. Cypress and Playwright cover the user flow. Run Postman in CI on every PR; run the browser suites on merge.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Signup + OTP smoke test on every deploy&lt;br&gt;
Provision a Temp Mail inbox, hit the signup endpoint with that address, poll the inbox for the OTP, and confirm. Three requests, one collection, runs in 8 seconds in Newman. Catches broken SMTP, broken templates, broken OTP generation, and broken activation endpoints in a single pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outbound webhook contract tests&lt;br&gt;
Create a Webhook Tester URL, register it as a subscription in your app, trigger the event that should fire it, then poll the YoBox endpoint to inspect the exact payload your service emitted. Catches schema drift before customers do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auth token refresh under load&lt;br&gt;
A pre-request script that conditionally refreshes the token means a 500-request collection only authenticates once instead of 500 times. Cuts CI minutes and avoids tripping your own rate limiter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disposable credentials per run&lt;br&gt;
Combine with the Password Generator so every Newman run creates an account with a unique high-entropy password. No fixture password ends up in a screen recording, a CI log, or a shared seed script.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;A Postman collection is only as good as what runs around it.&lt;/p&gt;

&lt;p&gt;Disposable email, webhook capture, and disposable credentials are the three primitives that move it from "happy-path GUI" to "real async API harness."&lt;/p&gt;

&lt;p&gt;Treat collection.json like source; review it in PRs.&lt;br&gt;
Replace fixture emails with YoBox Temp Mail addresses.&lt;br&gt;
Replace local listeners with YoBox Webhook Tester URLs captured into collection variables.&lt;br&gt;
Use the Password Generator for per-run credentials.&lt;br&gt;
Validate signature and token shapes with the RegEx Assistant before pasting patterns into Tests tabs.&lt;br&gt;
Pin Newman in CI and export JUnit so failures surface in your test reporter.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Postman becomes a CI-grade tool the moment you stop treating it as a request playground and start treating it as a versioned, scriptable, network-integrated test harness. Wire it to YoBox for the parts Postman doesn't own — email, webhooks, and disposable secrets — and you get end-to-end API coverage with nothing local to maintain. Pair it with the Cypress guide, the Playwright guide, and the Docker Builder recipe for full-stack confidence on every merge.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>postman</category>
      <category>api</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>AI Coding Assistants in 2026: What They Still Can't Do</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Wed, 17 Jun 2026 14:12:22 +0000</pubDate>
      <link>https://dev.to/yobox/ai-coding-assistants-in-2026-what-they-still-cant-do-2b35</link>
      <guid>https://dev.to/yobox/ai-coding-assistants-in-2026-what-they-still-cant-do-2b35</guid>
      <description>&lt;p&gt;AI coding assistants are now part of daily developer life.&lt;/p&gt;

&lt;p&gt;They write boilerplate, explain strange errors, generate tests, and save hours of repetitive work.&lt;/p&gt;

&lt;p&gt;But here is the truth: AI is powerful, not magical.&lt;/p&gt;

&lt;p&gt;At YoBox, we use AI while building practical developer tools. It helps us move faster, but it still needs human judgment.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where AI Coding Assistants Are Excellent
&lt;/h1&gt;

&lt;p&gt;AI is strongest when the task is clear, repetitive, and pattern-based.&lt;/p&gt;

&lt;p&gt;That makes it useful for everyday development work.&lt;/p&gt;

&lt;p&gt;Reality Check&lt;/p&gt;

&lt;p&gt;AI works best when the goal is clear.&lt;/p&gt;

&lt;p&gt;It is excellent at generating first drafts, but weak at understanding the full business context behind a feature.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Boilerplate Code
AI can quickly generate:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;React components&lt;br&gt;
API handlers&lt;br&gt;
Validation logic&lt;br&gt;
CRUD endpoints&lt;br&gt;
Form structures&lt;br&gt;
Test scaffolding&lt;br&gt;
This saves real time.&lt;/p&gt;

&lt;p&gt;Instead of spending 30 minutes writing boring setup code, you can ask AI for a first draft and then refine it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Documentation
AI is great at creating:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;README files&lt;br&gt;
API explanations&lt;br&gt;
Setup guides&lt;br&gt;
Code comments&lt;br&gt;
Internal documentation&lt;br&gt;
Most developers do not enjoy writing documentation.&lt;/p&gt;

&lt;p&gt;AI makes that job less painful.&lt;/p&gt;

&lt;p&gt;Why this matters&lt;/p&gt;

&lt;p&gt;Better documentation means fewer repeated questions, faster onboarding, and cleaner developer workflows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test Ideas
AI can suggest useful test cases, especially for:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Form validation&lt;br&gt;
API responses&lt;br&gt;
Signup flows&lt;br&gt;
OTP verification&lt;br&gt;
Webhook events&lt;br&gt;
For example, if you are testing signup flows, you can combine AI-generated Cypress or Playwright tests with YoBox Temp Mail to verify real email behavior.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Regex Help
Regex is still painful.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI can generate patterns quickly, but you should always test them before using them in production.&lt;/p&gt;

&lt;p&gt;Use YoBox Regex Assistant here:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/tools/regex-assistant" rel="noopener noreferrer"&gt;https://yobox.dev/tools/regex-assistant&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Never blindly trust generated regex.&lt;/p&gt;

&lt;p&gt;Test it first, especially when the pattern is used for validation, OTP extraction, URLs, emails, or security-sensitive input.&lt;/p&gt;

&lt;h1&gt;
  
  
  What AI Still Cannot Do Well
&lt;/h1&gt;

&lt;p&gt;AI is fast.&lt;/p&gt;

&lt;p&gt;But it still struggles with tasks that require deep system context.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cross-System Refactoring
Changing one file is easy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Changing a business rule across multiple systems is dangerous.&lt;/p&gt;

&lt;p&gt;Affected areas may include:&lt;/p&gt;

&lt;p&gt;Frontend&lt;br&gt;
Backend&lt;br&gt;
Database&lt;br&gt;
Payments&lt;br&gt;
Emails&lt;br&gt;
Analytics&lt;br&gt;
Free tool&lt;br&gt;
Try YoBox Temp Mail&lt;br&gt;
Disposable inbox — no signup, instant OTP.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
AI often misses hidden dependencies.&lt;/p&gt;

&lt;p&gt;A human developer understands product history, team decisions, and strange edge cases better than AI.&lt;/p&gt;

&lt;p&gt;AI understands patterns. Experienced engineers understand systems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performance Debugging
AI can suggest obvious improvements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But real performance issues require context.&lt;/p&gt;

&lt;p&gt;You need to understand:&lt;/p&gt;

&lt;p&gt;Database queries&lt;br&gt;
Network latency&lt;br&gt;
Caching&lt;br&gt;
User behavior&lt;br&gt;
Infrastructure limits&lt;br&gt;
Business priorities&lt;br&gt;
A 50ms improvement nobody notices may not matter.&lt;/p&gt;

&lt;p&gt;A 5ms improvement on a payment flow might be critical.&lt;/p&gt;

&lt;p&gt;AI can help investigate, but it cannot replace experience.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Product Judgment
Some problems look technical but are actually product decisions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;Should this be a modal or a full page?&lt;br&gt;
Should users receive an email notification?&lt;br&gt;
Should this action require verification?&lt;br&gt;
Should this flow be automated?&lt;br&gt;
AI can give options.&lt;/p&gt;

&lt;p&gt;It does not fully understand your users, your market, or your business model.&lt;/p&gt;

&lt;p&gt;The code is often easy. The decision is hard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Legacy Code
Legacy code is where AI starts sweating.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Old codebases often contain:&lt;/p&gt;

&lt;p&gt;Missing documentation&lt;br&gt;
Strange naming&lt;br&gt;
Dead code&lt;br&gt;
Workarounds&lt;br&gt;
Historical decisions nobody remembers&lt;br&gt;
AI performs best with clean patterns.&lt;/p&gt;

&lt;p&gt;Legacy systems are rarely clean.&lt;/p&gt;

&lt;p&gt;Legacy code is not just code. It is history.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security-Critical Logic
Never blindly trust AI with security.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI-generated code can introduce:&lt;/p&gt;

&lt;p&gt;Weak authentication&lt;br&gt;
Broken authorization&lt;br&gt;
Poor input validation&lt;br&gt;
Secret exposure&lt;br&gt;
Unsafe defaults&lt;br&gt;
Anything related to accounts, payments, sessions, permissions, or user data needs human review.&lt;/p&gt;

&lt;p&gt;Working code is not the same thing as secure code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key Benefits of Using AI Coding Assistants
&lt;/h1&gt;

&lt;p&gt;AI coding assistants are useful when used correctly.&lt;/p&gt;

&lt;p&gt;They help developers:&lt;/p&gt;

&lt;p&gt;Move faster&lt;br&gt;
Reduce repetitive work&lt;br&gt;
Generate better first drafts&lt;br&gt;
Explore unfamiliar technologies&lt;br&gt;
Create test ideas&lt;br&gt;
Improve documentation&lt;br&gt;
The best use case is acceleration, not automation without review.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real Use Cases
&lt;/h1&gt;

&lt;p&gt;Testing Signup Flows&lt;br&gt;
Imagine you want to test a signup flow.&lt;/p&gt;

&lt;p&gt;AI can help generate a Cypress or Playwright test.&lt;/p&gt;

&lt;p&gt;YoBox can provide the real tools:&lt;/p&gt;

&lt;p&gt;Use YoBox Temp Mail for a disposable inbox.&lt;br&gt;
Use YoBox Webhook Tester to inspect callback events.&lt;br&gt;
Use YoBox Password Generator for strong test credentials.&lt;br&gt;
Use YoBox Regex Assistant to validate extracted OTP patterns.&lt;br&gt;
AI writes the draft. YoBox validates the workflow. You make the final decision.&lt;/p&gt;

&lt;p&gt;Building Internal Tools&lt;br&gt;
AI can generate the first version of dashboards, admin panels, forms, and API wrappers.&lt;/p&gt;

&lt;p&gt;But your team still needs to review:&lt;/p&gt;

&lt;p&gt;Permissions&lt;br&gt;
Data access&lt;br&gt;
Error states&lt;br&gt;
Edge cases&lt;br&gt;
Security assumptions&lt;br&gt;
This is where human judgment protects the product.&lt;/p&gt;

&lt;p&gt;Debugging Integrations&lt;br&gt;
AI can help explain webhook payloads, API errors, and request failures.&lt;/p&gt;

&lt;p&gt;Pairing that with YoBox Webhook Tester makes debugging much faster:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/webhook" rel="noopener noreferrer"&gt;https://yobox.dev/webhook&lt;/a&gt;
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Internal Tools That Work Well With AI
&lt;/h1&gt;

&lt;p&gt;YoBox Temp Mail&lt;br&gt;
Use disposable inboxes to test signup, OTP, and email verification flows.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev" rel="noopener noreferrer"&gt;https://yobox.dev&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;YoBox Webhook Tester&lt;br&gt;
Inspect inbound HTTP requests in real time.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/webhook" rel="noopener noreferrer"&gt;https://yobox.dev/webhook&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;YoBox Docker Builder&lt;br&gt;
Generate clean Docker development setups faster.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/tools/docker-builder" rel="noopener noreferrer"&gt;https://yobox.dev/tools/docker-builder&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;YoBox Password Generator&lt;br&gt;
Create strong test credentials safely.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/tools/password-generator" rel="noopener noreferrer"&gt;https://yobox.dev/tools/password-generator&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;YoBox Regex Assistant&lt;br&gt;
Validate AI-generated regex before production use.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev/tools/regex-assistant" rel="noopener noreferrer"&gt;https://yobox.dev/tools/regex-assistant&lt;/a&gt;
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Common Mistakes Developers Make With AI
&lt;/h1&gt;

&lt;p&gt;Mistake 1: Copying Without Reading&lt;br&gt;
Generated code can look correct while hiding subtle problems.&lt;/p&gt;

&lt;p&gt;Always review it.&lt;/p&gt;

&lt;p&gt;Mistake 2: Skipping Tests&lt;br&gt;
AI output still needs tests.&lt;/p&gt;

&lt;p&gt;No test, no trust.&lt;/p&gt;

&lt;p&gt;Mistake 3: Asking Vague Questions&lt;br&gt;
A vague prompt produces vague code.&lt;/p&gt;

&lt;p&gt;Be specific about:&lt;/p&gt;

&lt;p&gt;Framework&lt;br&gt;
Input&lt;br&gt;
Output&lt;br&gt;
Constraints&lt;br&gt;
Error handling&lt;br&gt;
Mistake 4: Letting AI Make Product Decisions&lt;br&gt;
AI can help brainstorm.&lt;/p&gt;

&lt;p&gt;It should not decide your pricing, onboarding, security model, or product direction.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Are AI coding assistants useful in 2026?&lt;br&gt;
Yes. They are excellent for boilerplate, documentation, test ideas, debugging help, and learning new technologies.&lt;/p&gt;

&lt;p&gt;Can AI replace software developers?&lt;br&gt;
No. AI can automate repetitive work, but it still lacks deep product context, architecture judgment, and real-world responsibility.&lt;/p&gt;

&lt;p&gt;Is AI-generated code safe?&lt;br&gt;
Only after review and testing.&lt;/p&gt;

&lt;p&gt;Never deploy AI-generated code blindly.&lt;/p&gt;

&lt;p&gt;What is the biggest weakness of AI coding assistants?&lt;br&gt;
They often miss business context, hidden dependencies, and security risks.&lt;/p&gt;

&lt;p&gt;What tools work well with AI coding assistants?&lt;br&gt;
YoBox tools such as Temp Mail, Webhook Tester, Docker Builder, Password Generator, and Regex Assistant pair well with AI-assisted development.&lt;/p&gt;

&lt;p&gt;Where can I try YoBox?&lt;br&gt;
You can try YoBox for free here:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev" rel="noopener noreferrer"&gt;https://yobox.dev&lt;/a&gt;
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;AI coding assistants in 2026 are extremely useful.&lt;/p&gt;

&lt;p&gt;They are fast, helpful, and often impressive.&lt;/p&gt;

&lt;p&gt;But they still need human direction.&lt;/p&gt;

&lt;p&gt;The winning formula&lt;/p&gt;

&lt;p&gt;Use AI for speed.&lt;/p&gt;

&lt;p&gt;Use developer judgment for correctness.&lt;/p&gt;

&lt;p&gt;Use real tools like YoBox to validate the workflow.&lt;/p&gt;

&lt;p&gt;If you are building, testing, or debugging developer workflows, try the free YoBox tools here:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://yobox.dev" rel="noopener noreferrer"&gt;https://yobox.dev&lt;/a&gt;
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>devops</category>
    </item>
    <item>
      <title>Disposable Email vs Real Email vs Aliases: Which Should You Use?</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Tue, 16 Jun 2026 17:14:39 +0000</pubDate>
      <link>https://dev.to/yobox/disposable-email-vs-real-email-vs-aliases-which-should-you-use-4p4i</link>
      <guid>https://dev.to/yobox/disposable-email-vs-real-email-vs-aliases-which-should-you-use-4p4i</guid>
      <description>&lt;p&gt;If "just use a temp email" or "just use Gmail" felt like sufficient advice in 2015, it doesn't anymore. In 2026 you have at least three meaningful options for any given signup, and picking the wrong one will either flood you with spam, lock you out of an account, or both.&lt;/p&gt;

&lt;p&gt;This guide breaks down the three real options — disposable email, email aliases, and your real inbox — and gives you a decision framework you can apply in five seconds at any signup form.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Three Options
&lt;/h1&gt;

&lt;p&gt;Disposable Email (Temp Mail)&lt;br&gt;
A throwaway address that lives for minutes to hours. No signup, no password, no way to log back in. Built for one-off receiving.&lt;/p&gt;

&lt;p&gt;Examples: YoBox Temp Mail, mail.tm, 10minutemail.&lt;/p&gt;

&lt;p&gt;Email Aliases&lt;br&gt;
A permanent forwarding address that points to your real inbox. You generate one per service. If it leaks, you kill it. The original inbox stays clean.&lt;/p&gt;

&lt;p&gt;Examples: SimpleLogin, Apple Hide My Email, ProtonPass, Firefox Relay, AnonAddy.&lt;/p&gt;

&lt;p&gt;Real Email&lt;br&gt;
Your primary mailbox. Tied to your identity. The one your bank, employer, and government know you by.&lt;/p&gt;

&lt;p&gt;Examples: Gmail, Outlook, ProtonMail, Fastmail, your work address.&lt;/p&gt;

&lt;h1&gt;
  
  
  Side-by-Side Comparison
&lt;/h1&gt;

&lt;p&gt;Feature Disposable  Alias   Real&lt;br&gt;
Lifetime    Minutes–hours Permanent (until killed)    Permanent&lt;br&gt;
Forwards to your real inbox No  Yes N/A&lt;br&gt;
Signup required No  Yes (once)  Yes&lt;br&gt;
Can you reply?  No  Yes Yes&lt;br&gt;
Account recovery possible   No  Yes Yes&lt;br&gt;
Spam protection Total (inbox dies)  High (kill alias)   Low&lt;br&gt;
Identity exposure   Minimal Pseudonymous    Full&lt;br&gt;
Blocklisted by sites    Often   Rarely  Never&lt;br&gt;
Best for    One-offs    Per-service permanent IDs   Critical accounts&lt;/p&gt;

&lt;h1&gt;
  
  
  The Decision Framework
&lt;/h1&gt;

&lt;p&gt;Ask yourself one question at every signup form: "If I lose access to this account, what happens?"&lt;/p&gt;

&lt;p&gt;Nothing. I'll never come back. → Disposable email.&lt;br&gt;
Annoying, but I could re-create it. → Alias.&lt;br&gt;
I would be genuinely upset / it would cost me money. → Real email.&lt;br&gt;
That's the whole framework. Two seconds, every time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Worked Examples
&lt;/h1&gt;

&lt;p&gt;Let's run through real signups so this stops being abstract.&lt;/p&gt;

&lt;p&gt;"Free PDF download in exchange for your email"&lt;br&gt;
Free tool&lt;br&gt;
Try YoBox Temp Mail&lt;br&gt;
Disposable inbox — no signup, instant OTP.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
You will never log into this. The PDF gets emailed once. Disposable. Open YoBox Temp Mail, copy the address, paste, download, close tab.&lt;/p&gt;

&lt;p&gt;Reddit, Hacker News, niche forum&lt;br&gt;
You want to participate. You might come back. You don't want their newsletter. Alias. A SimpleLogin address forwards to your real inbox; you reply normally; if they start spamming, kill the alias.&lt;/p&gt;

&lt;p&gt;Bank, brokerage, government portal&lt;br&gt;
Real email. Always. Recovery matters more than privacy here, and these services will refuse disposable / alias addresses anyway.&lt;/p&gt;

&lt;p&gt;Stripe / Shopify for a side project&lt;br&gt;
Real email or a dedicated business address. You will absolutely need to receive billing emails, dispute notifications, and security alerts.&lt;/p&gt;

&lt;p&gt;Discord / Reddit for an alt account&lt;br&gt;
This is the trickiest case. Both platforms blocklist most disposable domains. See "Temporary Email for Discord" and "Temporary Email for GitHub" for the workarounds — short version: aliases work, most disposable services don't.&lt;/p&gt;

&lt;p&gt;Testing your own app's signup flow&lt;br&gt;
Disposable, every time. You don't want 200 "welcome to MyApp!" emails in your real inbox while you're QA'ing. The YoBox Temp Mail API plugs into Cypress and Playwright cleanly.&lt;/p&gt;

&lt;p&gt;Online shopping at a store you might use again&lt;br&gt;
Alias. If you ever need warranty service, the alias still works. If they sell your address, you kill it.&lt;/p&gt;

&lt;p&gt;Newsletter you might unsubscribe from&lt;br&gt;
Alias. Subscribe via alias; if you keep it, fine; if not, kill the alias and the unsubscribe link becomes irrelevant.&lt;/p&gt;

&lt;p&gt;Beta access to a startup that might not exist next year&lt;br&gt;
Disposable. If the product is good, you can re-sign up with a real address later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;p&gt;Using disposable for things you'll want to log back into&lt;br&gt;
The most common regret. You sign up for a service with a 10-minute address, decide weeks later you want to log in, request a password reset… and the email goes nowhere.&lt;/p&gt;

&lt;p&gt;Using your real email for everything&lt;br&gt;
Every breach increases the value of your address on data-broker lists. Every newsletter adds noise. Every site you forget about is a potential 2030 leak with your name on it.&lt;/p&gt;

&lt;p&gt;Using plus-addressing as a privacy tool&lt;br&gt;
&lt;a href="mailto:you+netflix@gmail.com"&gt;you+netflix@gmail.com&lt;/a&gt; is a filtering tool, not a privacy tool. Spam lists strip the +tag. The base address is in plaintext. Don't confuse plus-addressing with aliasing.&lt;/p&gt;

&lt;p&gt;Using the same alias for multiple services&lt;br&gt;
Defeats the point. The whole reason to use aliases is one-per-service, so a leak narrows immediately.&lt;/p&gt;

&lt;p&gt;Trusting "anonymous" disposable email&lt;br&gt;
Most temp mail providers log IPs. Many show inboxes publicly. Disposable email is great for spam protection; it is not a security tool. If you need real anonymity, you need Tor + a service that doesn't log + a pseudonym you never reuse — and even then it's hard.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Hybrid Workflow That Works
&lt;/h1&gt;

&lt;p&gt;This is the setup we use and recommend:&lt;/p&gt;

&lt;p&gt;One real address. ProtonMail, Fastmail, or Gmail — whatever you trust. Used only for Tier 1 (bank, work, government, password manager).&lt;br&gt;
An alias service. SimpleLogin or Apple Hide My Email. New alias per service. Default for almost everything.&lt;br&gt;
Disposable on demand. YoBox Temp Mail for one-off downloads, testing, and beta signups.&lt;br&gt;
The alias service is the keystone. It's the one most people skip and the one that does the most work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Developer Note
&lt;/h1&gt;

&lt;p&gt;If you're building an app and want to block disposable email, blocklists exist (e.g. disposable-email-domains on GitHub) but they're noisy and constantly out of date. A better approach: verify with a code, accept disposable signups for low-trust actions, and require phone verification before high-trust ones. Pair the email verification with a webhook handler and you have a full audit trail.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is an alias more secure than disposable email?&lt;br&gt;
For recovery and ongoing use, yes. For one-off anonymous receiving, disposable wins because it leaves no thread back to you.&lt;/p&gt;

&lt;p&gt;Can I use Apple Hide My Email for everything?&lt;br&gt;
You can, but it only works inside Apple's ecosystem (Safari, iCloud). SimpleLogin or AnonAddy work everywhere.&lt;/p&gt;

&lt;p&gt;Will sites detect aliases the way they detect disposable email?&lt;br&gt;
Some do for major alias services. Most don't. Aliases generally survive blocklists much better than disposable inboxes.&lt;/p&gt;

&lt;p&gt;What about using Gmail's dots trick?&lt;br&gt;
Same as plus-addressing — a filtering convenience, not privacy. Spammers ignore the dots.&lt;/p&gt;

&lt;p&gt;Do I really need three different email strategies?&lt;br&gt;
You don't need to. You'll regret not having them the day a major service you signed up for in 2020 gets breached and your address shows up on HaveIBeenPwned.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Disposable email is for fire-and-forget. Aliases are for "I want to receive mail, just not have my real address out there." Real email is for "if I lose this account, my life gets worse." Pick the right tool for the right form, and the next decade of your inbox will be quieter than the last.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Use DisposableEmail Safely (WithoutLocking Yourself Out)</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Mon, 15 Jun 2026 16:25:04 +0000</pubDate>
      <link>https://dev.to/yobox/how-to-use-disposableemail-safely-withoutlocking-yourself-out-2emk</link>
      <guid>https://dev.to/yobox/how-to-use-disposableemail-safely-withoutlocking-yourself-out-2emk</guid>
      <description>&lt;p&gt;Disposable email feels like a cheat code. Hand the form an address that exists for an hour, get whatever you came for, walk away. No spam. No mailing list. No "we noticed you haven't logged in" guilt-trips three years later.&lt;/p&gt;

&lt;p&gt;But disposable email has a dark side, and it's not a security one — it's a self-inflicted one. The number-one reason people regret using temp mail is locking themselves out of an account they actually wanted to keep. This guide is about avoiding that, and about using disposable email in a way that actually improves your security posture instead of quietly hurting it.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Single Rule
&lt;/h1&gt;

&lt;p&gt;Before any tactics: never use disposable email for an account you'd be upset to lose.&lt;/p&gt;

&lt;p&gt;That's the whole framework. Everything else is a refinement of how to tell which accounts those are. If you'd cry over losing it — your bank, your domain registrar, your Apple ID, your Steam library, your GitHub, your Stripe — use a real email or an alias. If you wouldn't blink — a forum, a one-time download, a coupon — disposable is great.&lt;/p&gt;

&lt;h1&gt;
  
  
  When Disposable Email Is the Right Tool
&lt;/h1&gt;

&lt;p&gt;These are the canonical good fits:&lt;/p&gt;

&lt;p&gt;Downloading a "free" resource that requires email (PDFs, whitepapers, lead magnets).&lt;br&gt;
Reading a forum post locked behind a signup wall.&lt;br&gt;
One-time purchases from a vendor you'll never buy from again.&lt;br&gt;
Testing your own software. Sign up to your own app to make sure the email flow works.&lt;br&gt;
Beta access to a service you might never actually use.&lt;br&gt;
Newsletter previews. Subscribe, read one issue, decide.&lt;br&gt;
For the developer cases, the YoBox Temp Mail tool is purpose-built — fast addresses, OTP-friendly delivery, and a JSON API for automated tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  When Disposable Email Will Hurt You
&lt;/h1&gt;

&lt;p&gt;These are the cases where users get burned:&lt;/p&gt;

&lt;p&gt;Account recovery. No real address → no password reset → permanent lockout.&lt;br&gt;
Two-factor backup codes. If your 2FA email is the disposable one, you're a phone-loss away from disaster.&lt;br&gt;
Receipts you'll need later. Tax time will find you.&lt;br&gt;
Anything tied to a phone number. When the SMS comes asking you to "confirm via email," you'll be hunting for an inbox that no longer exists.&lt;br&gt;
Subscriptions. Renewal warnings, billing changes, and cancellation confirmations all go to email.&lt;/p&gt;

&lt;h1&gt;
  
  
  A Tiered Strategy for Real Life
&lt;/h1&gt;

&lt;p&gt;Free tool&lt;br&gt;
Try YoBox Temp Mail&lt;br&gt;
Disposable inbox — no signup, instant OTP.&lt;/p&gt;

&lt;p&gt;Open&lt;br&gt;
The cleanest way to think about email isn't "real vs disposable" — it's a three-tier system:&lt;/p&gt;

&lt;p&gt;Tier    Use for Tool&lt;br&gt;
Tier 1: Real    Bank, government, work, anything you'd cry to lose  Gmail / Outlook / ProtonMail&lt;br&gt;
Tier 2: Alias   Per-service permanent identities, online shopping, social media SimpleLogin, Apple Hide My Email, ProtonPass&lt;br&gt;
Tier 3: Disposable  One-off signups, downloads, testing YoBox Temp Mail&lt;br&gt;
Tier 2 is the one most people skip and most regret skipping. Aliases give you a unique address per service that forwards to your real inbox — so when you start getting spam to netflix-2023@yourdomain, you know exactly who leaked. You can kill the alias without touching your real address.&lt;/p&gt;

&lt;h1&gt;
  
  
  Safety Practices for Disposable Inboxes
&lt;/h1&gt;

&lt;p&gt;If you're going to use temp mail, do it right:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Copy the address — and the inbox URL&lt;br&gt;
Most temp mail services let you re-open the same inbox later if you saved the URL. YoBox stores your address locally so it survives a page reload. Always copy the address and note the service, in case you need to come back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read the email before closing the tab&lt;br&gt;
This sounds obvious. It is not. Half the "I lost access" stories start with "I copied the OTP, pasted it, closed the tab, and then the site said wait for a second verification email."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't reuse a disposable address for a second account&lt;br&gt;
Disposable inboxes are often public or guessable. If you used one for Account A and someone else gets the same address tomorrow, they can request a password reset on Account A and read the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treat OTPs as time-sensitive&lt;br&gt;
A 6-digit code with a 10-minute window means a 10-minute disposable inbox is fine, but a 60-second one is not. See "Why OTP Verification Fails (and How to Fix It)" for more on timing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never paste sensitive content into a temp inbox preview&lt;br&gt;
Some services display message bodies in plaintext on shared infrastructure. If a sender accidentally emails you something private (an API key, a contract, a personal note), don't assume the inbox is private just because it's "yours."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be aware of legal grey zones&lt;br&gt;
Most sites' ToS forbid disposable email. You won't be sued, but your account can be terminated without notice — including any data inside it. Don't store anything important there. See "Is Disposable Email Legal and Safe?".&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  A Workflow That Works
&lt;/h1&gt;

&lt;p&gt;Here's the actual workflow we use:&lt;/p&gt;

&lt;p&gt;Default to an alias. SimpleLogin or Apple Hide My Email for almost everything. One alias per service.&lt;br&gt;
Use disposable for true one-offs. PDF downloads, beta signups, things you'll never log into again.&lt;br&gt;
Use the real address only for tier 1. Bank, work, government, critical accounts.&lt;br&gt;
Keep a password manager. Disposable addresses become very, very hard to remember; if you ever do need to log back in, a password manager that remembers &lt;a href="mailto:a8f3kq@somedomain.com"&gt;a8f3kq@somedomain.com&lt;/a&gt; saves you.&lt;/p&gt;

&lt;h1&gt;
  
  
  Disposable Email for Developers
&lt;/h1&gt;

&lt;p&gt;If you're a developer testing your own app, disposable email is the correct tool, not a workaround. Don't pollute your real inbox with 400 password resets while you're QAing the auth flow. Use a disposable address. The YoBox Temp Mail JSON API lets you spin up a fresh inbox programmatically, trigger a signup, and read the OTP — see "Email Testing Guide for Developers" for code samples.&lt;/p&gt;

&lt;p&gt;Pair it with the Webhook Tester when your signup flow also fires a backend webhook (Mailgun, Postmark, SendGrid all do this) and you want to assert on both halves of the flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I use disposable email for online shopping?&lt;br&gt;
For one-time purchases, sure — but use an alias if you might need warranty or return support.&lt;/p&gt;

&lt;p&gt;What if the site detects disposable email and blocks me?&lt;br&gt;
Try a different provider with a fresher domain. Some services rotate domains specifically to dodge blocklists.&lt;/p&gt;

&lt;p&gt;Is it safe to receive password reset codes on a temp address?&lt;br&gt;
Only for accounts you don't care about losing. If the account has anything important, use a real address.&lt;/p&gt;

&lt;p&gt;Can I migrate an account from a disposable email to a real one?&lt;br&gt;
Most sites let you change your account email from inside settings — but only if you can still log in. Plan ahead.&lt;/p&gt;

&lt;p&gt;Does using temp mail mark me as a spammer?&lt;br&gt;
Not directly. But many sites distrust disposable signups and gate features (e.g. Reddit, Discord) until you "upgrade" to a real address.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Disposable email is a scalpel, not a hammer. Use it for the things it's good at — anonymous one-offs, developer testing, dodging marketing lists — and use aliases or real addresses for everything else. The YoBox Temp Mail tool is built for the cases where temp mail is genuinely the right answer; the rest of the time, an alias service or your real inbox will serve you better.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>qa</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Email Testing Guide for Developers (2026 Edition)</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sun, 14 Jun 2026 11:16:38 +0000</pubDate>
      <link>https://dev.to/yobox/email-testing-guide-for-developers-2026-edition-2p7n</link>
      <guid>https://dev.to/yobox/email-testing-guide-for-developers-2026-edition-2p7n</guid>
      <description>&lt;p&gt;Email is the test surface nobody wants to own. It's asynchronous, depends on external providers, has its own version of "flaky" (sometimes a delivery just… takes 90 seconds), and the bugs that bite hardest in production — wrong template variables, broken unsubscribe links, OTPs that expire too fast — are the ones unit tests can't catch.&lt;/p&gt;

&lt;p&gt;This guide is the working developer's playbook for testing email in 2026. We'll cover the three layers (unit, integration, end-to-end), the tooling for each, and the patterns that actually scale.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Three Layers of Email Testing
&lt;/h1&gt;

&lt;p&gt;| Layer | What you're testing | Tooling | |---|---|---| | Unit | Template rendering, variable substitution, plain-text fallback | Jest / Vitest with snapshot tests | | Integration | Your app correctly calls your email provider's API | Provider sandbox or mock SDK | | End-to-end | The email actually arrives and the user can complete the flow | YoBox Temp Mail + Cypress / Playwright |&lt;/p&gt;

&lt;p&gt;Most teams cover unit. Many cover integration. Few cover end-to-end — and that's exactly where the production bugs live.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layer 1: Unit Tests for Templates
&lt;/h1&gt;

&lt;p&gt;If you're using a templating engine (MJML, Handlebars, JSX-email, React Email), render the template with test data and snapshot the output. Catches:&lt;/p&gt;

&lt;p&gt;Missing variable substitutions (Hello, {{name}} rendered with no name)&lt;br&gt;
Broken HTML&lt;br&gt;
Missing plain-text fallback&lt;br&gt;
Localization bugs&lt;br&gt;
test('welcome email renders', () =&amp;gt; {&lt;br&gt;
  const html = renderWelcomeEmail({ name: 'Ada', verifyUrl: '&lt;a href="https://example.com/v/abc" rel="noopener noreferrer"&gt;https://example.com/v/abc&lt;/a&gt;' });&lt;br&gt;
  expect(html).toMatchSnapshot();&lt;br&gt;
  expect(html).toContain('Hello, Ada');&lt;br&gt;
  expect(html).toContain('&lt;a href="https://example.com/v/abc'" rel="noopener noreferrer"&gt;https://example.com/v/abc'&lt;/a&gt;);&lt;br&gt;
});&lt;br&gt;
These tests are cheap, fast, and catch the "template rendered with undefined literally in the body" class of bugs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layer 2: Integration Tests for Send Logic
&lt;/h1&gt;

&lt;p&gt;Your app calls Postmark / SendGrid / SES / Resend with a payload. The integration test asserts that you call the provider with the right payload, not that the email arrives.&lt;/p&gt;

&lt;p&gt;Most providers ship a test mode or a sandbox endpoint. Use it.&lt;/p&gt;

&lt;p&gt;test('signup triggers verification email', async () =&amp;gt; {&lt;br&gt;
  const sendSpy = vi.spyOn(emailProvider, 'send');&lt;br&gt;
  await signup({ email: '&lt;a href="mailto:test@example.com"&gt;test@example.com&lt;/a&gt;' });&lt;br&gt;
  expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({&lt;br&gt;
    to: '&lt;a href="mailto:test@example.com"&gt;test@example.com&lt;/a&gt;',&lt;br&gt;
    template: 'verify-email',&lt;br&gt;
    variables: expect.objectContaining({ code: expect.stringMatching(/^\d{6}$/) }),&lt;br&gt;
  }));&lt;br&gt;
});&lt;br&gt;
You'll catch: - Wrong recipient - Wrong template - Missing variables - Codes outside expected format&lt;/p&gt;

&lt;p&gt;You will not catch: - The email being rejected by Gmail's spam filter - The OTP arriving 90 seconds late - The magic link 404'ing because the route changed&lt;/p&gt;

&lt;p&gt;Those need end-to-end.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layer 3: End-to-End with Disposable Inboxes
&lt;/h1&gt;

&lt;p&gt;This is where most teams give up. Don't.&lt;/p&gt;

&lt;p&gt;The pattern, in one paragraph: spin up a real disposable inbox via API, submit a real signup with that address, let the email actually traverse SMTP, poll the inbox until the message arrives, parse out the OTP or link, submit it back to your app, assert the user is now signed in.&lt;/p&gt;

&lt;p&gt;Full implementation in "How to Test Email Flows Without a Real Inbox" — works in Cypress, Playwright, or any HTTP-capable runner.&lt;/p&gt;

&lt;p&gt;The YoBox Temp Mail API is designed for this loop: no auth, no rate limit on normal use, no captcha. The address generates in under a second, OTPs typically arrive in 2–5 seconds, and inboxes persist long enough for slow senders (SES, in-house SMTP).&lt;/p&gt;

&lt;h1&gt;
  
  
  Pair with Webhook Testing
&lt;/h1&gt;

&lt;p&gt;Many email flows also fire downstream webhooks. SendGrid fires processed, delivered, opened; Postmark fires Delivery, Open, Click. To fully test, point those webhooks at the YoBox Webhook Tester during your test run, and assert on both halves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger signup&lt;/li&gt;
&lt;li&gt;Wait for OTP email&lt;/li&gt;
&lt;li&gt;Verify code&lt;/li&gt;
&lt;li&gt;Wait for user.verified webhook on your test endpoint&lt;/li&gt;
&lt;li&gt;Assert on both
This catches the production-only bugs: the email sent but the webhook didn't fire (bad downstream config), the webhook fired but with wrong payload, the order-of-operations bug where the user clicks before the webhook lands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Common Test Scenarios
&lt;/h1&gt;

&lt;p&gt;Signup with OTP&lt;br&gt;
test('signup', async ({ page, request }) =&amp;gt; {&lt;br&gt;
  const inbox = await createTempInbox(request);&lt;br&gt;
  await page.goto('/signup');&lt;br&gt;
  await page.fill('[name=email]', inbox.address);&lt;br&gt;
  await page.click('text=Send code');&lt;br&gt;
  const code = await pollForOtp(request, inbox.token);&lt;br&gt;
  await page.fill('[name=otp]', code);&lt;br&gt;
  await page.click('text=Verify');&lt;br&gt;
  await expect(page).toHaveURL(/dashboard/);&lt;br&gt;
});&lt;br&gt;
Password Reset&lt;br&gt;
test('password reset', async ({ page, request }) =&amp;gt; {&lt;br&gt;
  // (assume user already exists at inbox.address)&lt;br&gt;
  await page.goto('/forgot');&lt;br&gt;
  await page.fill('[name=email]', existingUser.email);&lt;br&gt;
  await page.click('text=Reset password');&lt;br&gt;
  const msg = await pollForEmail(request, existingUser.token);&lt;br&gt;
  const link = msg.text.match(/https:\/\/[^\s]+\/reset\?token=\S+/)?.[0];&lt;br&gt;
  await page.goto(link);&lt;br&gt;
  await page.fill('[name=password]', 'new-pw');&lt;br&gt;
  await page.click('text=Save');&lt;br&gt;
  await expect(page.locator('text=Password updated')).toBeVisible();&lt;br&gt;
});&lt;br&gt;
Double Opt-In&lt;br&gt;
test('newsletter confirmation', async ({ page, request }) =&amp;gt; {&lt;br&gt;
  const inbox = await createTempInbox(request);&lt;br&gt;
  await page.goto('/subscribe');&lt;br&gt;
  await page.fill('[name=email]', inbox.address);&lt;br&gt;
  await page.click('text=Subscribe');&lt;br&gt;
  const msg = await pollForEmail(request, inbox.token);&lt;br&gt;
  const confirmLink = msg.text.match(/https:\/\/[^\s]+\/confirm\?\S+/)?.[0];&lt;br&gt;
  await page.goto(confirmLink);&lt;br&gt;
  await expect(page.locator('text=Subscription confirmed')).toBeVisible();&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  Deliverability Testing
&lt;/h1&gt;

&lt;p&gt;Unit and integration tests can't tell you if Gmail will route your message to spam. For that, you need:&lt;/p&gt;

&lt;p&gt;Mail-tester.com for one-off checks (paste a generated address, send your email, get a deliverability score).&lt;br&gt;
GlockApps or similar for ongoing inbox-placement monitoring.&lt;br&gt;
DMARC reports for production sender health.&lt;br&gt;
These aren't replacements for the test layers above; they're a separate axis.&lt;/p&gt;

&lt;h1&gt;
  
  
  Anti-Patterns to Avoid
&lt;/h1&gt;

&lt;p&gt;Snapshot the entire HTML email and break on every CSS tweak. Snapshot the key text + structure, not the whole document.&lt;br&gt;
Use your real personal Gmail for test signups. You'll regret it.&lt;br&gt;
Stub email entirely in E2E tests. You'll miss the bugs E2E exists to find.&lt;br&gt;
Share one disposable inbox across parallel tests. Race conditions on messages.&lt;br&gt;
Set OTP TTL to 60 seconds "for security." Real users with slow inboxes can't complete the flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tooling Cheat Sheet
&lt;/h1&gt;

&lt;p&gt;| Need | Tool | |---|---| | Render template snapshots | Jest / Vitest | | Mock email provider SDK | provider's own test mode or vi.spyOn | | Disposable inbox for E2E | YoBox Temp Mail | | Capture downstream webhooks | YoBox Webhook Tester | | Local SMTP catcher for dev | Mailhog, MailCatcher | | Deliverability score | mail-tester.com | | Inbox-placement monitoring | GlockApps |&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Should I test against staging or production email providers? Staging if available. Production providers usually have a test mode (Postmark "Sandbox", SendGrid "Sandbox Mode") that returns success without actually delivering.&lt;/p&gt;

&lt;p&gt;How do I avoid hitting send rate limits in CI? Cap parallelism, use disposable addresses (so the recipient never hits its rate limit), and reserve full E2E runs for nightly builds.&lt;/p&gt;

&lt;p&gt;Can I run these tests in GitHub Actions? Yes. YoBox Temp Mail and Webhook Tester have no auth and no captcha, so they work in any CI.&lt;/p&gt;

&lt;p&gt;What about testing email in mobile apps? Same pattern — the disposable address is platform-agnostic, you just drive the mobile UI with Detox or Appium instead of Playwright.&lt;/p&gt;

&lt;p&gt;Do I need a separate domain for test emails? Not strictly. But sending from a different from address in tests (e.g. test@yourdomain) keeps your production sender reputation clean.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Email testing has three layers and you should cover all three. Unit tests catch template bugs. Integration tests catch send-logic bugs. End-to-end tests with disposable inboxes and webhook capture catch the bugs that only show up when the email actually flies. Skip any layer and you'll find the bug in production instead of CI.&lt;/p&gt;

&lt;p&gt;A practical guide to OTP verification, password resets, transactional emails, deliverability testing, and the workflows modern developers and QA teams use to validate email systems in 2026.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>webdev</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Docker Builder Recipe for Cypress &amp; Playwright in CI</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sat, 13 Jun 2026 11:06:01 +0000</pubDate>
      <link>https://dev.to/yobox/a-docker-builder-recipe-for-cypress-playwright-in-ci-h64</link>
      <guid>https://dev.to/yobox/a-docker-builder-recipe-for-cypress-playwright-in-ci-h64</guid>
      <description>&lt;p&gt;Containerized e2e is the difference between a green build and a four-hour debugging session over Slack DM. "Works on my machine" stops being funny the third time it happens. This guide gives you a battle-tested Dockerfile and docker-compose.yml for running Cypress and Playwright in CI, plus the YoBox plumbing that keeps disposable inboxes and webhook receivers out of your container image.&lt;/p&gt;

&lt;p&gt;The Docker Builder tool scaffolds the baseline; this article explains the why behind every line.&lt;/p&gt;

&lt;h1&gt;
  
  
  What good looks like
&lt;/h1&gt;

&lt;p&gt;A solid e2e container has five properties:&lt;/p&gt;

&lt;p&gt;Deterministic — same image, same result, this year and next.&lt;br&gt;
Cached — node_modules and browser binaries don't re-download every run.&lt;br&gt;
Shardable — N replicas, N× faster, no shared state.&lt;br&gt;
Small enough — under ~1.5 GB so pulls don't dominate wall-clock time.&lt;br&gt;
Observable — traces, screenshots, and videos survive the run.&lt;br&gt;
YoBox helps with property 3 by giving every shard its own disposable inbox and webhook URL over plain HTTP. No mail server in the compose file, no tunnel sidecar.&lt;/p&gt;
&lt;h1&gt;
  
  
  Base image choice
&lt;/h1&gt;

&lt;p&gt;Both Playwright and Cypress publish official images. They are large but they save you from chasing missing Chromium libs at 1 AM. Use them.&lt;/p&gt;
&lt;h1&gt;
  
  
  Playwright
&lt;/h1&gt;

&lt;p&gt;FROM mcr.microsoft.com/playwright:v1.49.0-jammy&lt;/p&gt;
&lt;h1&gt;
  
  
  Cypress
&lt;/h1&gt;

&lt;p&gt;FROM cypress/included:14.0.0&lt;br&gt;
If you need both in one image (rare, but useful for golden tests), start from the Playwright image and npm i cypress on top — Cypress brings its own Electron, Playwright brings its own browsers, and they coexist fine.&lt;/p&gt;

&lt;p&gt;رA production Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```dockerfile FROM mcr.microsoft.com/playwright:v1.49.0-jammy&lt;/p&gt;

&lt;p&gt;WORKDIR /app&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Dependencies — separate layer for cache reuse COPY package.json package-lock.json ./ RUN npm ci --no-audit --no-fund
&lt;/h1&gt;

&lt;h1&gt;
  
  
  2. Browsers (Playwright auto-installs in base image; uncomment if pinning) # RUN npx playwright install --with-deps
&lt;/h1&gt;

&lt;h1&gt;
  
  
  3. Source COPY . .
&lt;/h1&gt;

&lt;h1&gt;
  
  
  4. Default env ENV CI=1 \ YOBOX=&lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt; \ PWDEBUG=0
&lt;/h1&gt;

&lt;p&gt;ENTRYPOINT ["npx", "playwright", "test"] ```&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Key choices:&lt;/p&gt;

&lt;p&gt;npm ci not npm install — reproducible installs.&lt;br&gt;
Source copied after deps so a one-line spec change doesn't blow the cache.&lt;br&gt;
YOBOX baked in so tests have a sane default but can be overridden per environment.&lt;/p&gt;
&lt;h1&gt;
  
  
  docker-compose for local parity
&lt;/h1&gt;



&lt;p&gt;```yaml services: app: build: ./web ports: ["3000:3000"]&lt;/p&gt;

&lt;p&gt;e2e: build: ./tests environment: - BASE_URL=&lt;a href="http://app:3000" rel="noopener noreferrer"&gt;http://app:3000&lt;/a&gt; - YOBOX=&lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt; depends_on: - app command: ["--shard=1/1"] ```&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;That's it. No mail server. No webhook tunnel. The tests reach YoBox over the public internet, which is exactly what CI does too — meaning your local run matches CI byte-for-byte.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sharding in CI
&lt;/h1&gt;

&lt;p&gt;GitHub Actions, four shards:&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  e2e:&lt;br&gt;
    strategy:&lt;br&gt;
      matrix: { shard: ["1/4", "2/4", "3/4", "4/4"] }&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - run: docker compose build e2e&lt;br&gt;
      - run: docker compose run --rm e2e --shard=${{ matrix.shard }}&lt;br&gt;
Because every test asks YoBox for its own inbox and its own webhook URL, the shards never collide.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cache strategy
&lt;/h1&gt;

&lt;p&gt;Image pulls are the single biggest cost in containerized e2e. Two tricks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uses: docker/setup-buildx-action@v3&lt;/li&gt;
&lt;li&gt;uses: docker/build-push-action@v6
with:
context: ./tests
cache-from: type=gha
cache-to: type=gha,mode=max
load: true
tags: e2e:latest
GHA's registry cache turns a cold 4-minute build into a 20-second warm build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Trace and video artifacts
&lt;/h1&gt;

&lt;p&gt;Mount an output volume and upload on failure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run: docker compose run --rm -v "$PWD/test-results:/app/test-results" e2e&lt;/li&gt;
&lt;li&gt;if: failure()
uses: actions/upload-artifact@v4
with:
name: traces-${{ matrix.shard }}
path: test-results
Pair Playwright's trace: "on-first-retry" with this and every flaky failure ships you a viewable trace.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Image size: what actually matters
&lt;/h1&gt;

&lt;p&gt;| Layer | Size | Notes | | -------------------- | -------- | ------------------------------------ | | Playwright base | ~1.2 GB | Includes Chromium, Firefox, WebKit. | | Cypress base | ~1.4 GB | Includes Electron + Xvfb. | | node_modules | 200–500 MB | Cacheable separately. | | Source | &amp;lt;10 MB | Negligible. |&lt;/p&gt;

&lt;p&gt;Stripping browsers you don't use saves more than micro-optimizing layers. Pick one browser engine per suite when you can.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pairing with YoBox
&lt;/h1&gt;

&lt;p&gt;Inside the container, every helper is a one-liner:&lt;/p&gt;

&lt;p&gt;const inbox = await fetch(process.env.YOBOX + "/mail/new", { method: "POST" }).then(r =&amp;gt; r.json());&lt;br&gt;
That's all the integration code you need. The Cypress guide and Playwright guide cover the full fixture patterns.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;npm install in CI. Always npm ci. Always.&lt;br&gt;
--ipc=host missing for Chromium. Without it, Chromium crashes under load. docker run --ipc=host ...&lt;br&gt;
Mounting the host node_modules. Don't. Native modules differ between host and container.&lt;br&gt;
No browser pinning. Tag the Playwright base image with an exact version. latest will betray you.&lt;br&gt;
Skipping retries. Set retries: 1 in CI to absorb single-request blips without masking real bugs.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I run this on ARM (M-series Macs)? Yes — both Playwright and Cypress publish multi-arch images.&lt;/p&gt;

&lt;p&gt;How do I avoid pulling the image every run? Use a self-hosted runner with a Docker volume, or GHA's registry cache.&lt;/p&gt;

&lt;p&gt;Should I bake node_modules into the image? Yes for CI, no for local dev where you bind-mount source.&lt;/p&gt;

&lt;p&gt;Where do I store test reports? Upload as an artifact and link from the PR. Don't commit them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Containerized e2e is non-negotiable for any team running tests across more than one machine. The recipe above — official base image, npm ci, sharded compose, GHA cache, YoBox-backed inboxes and webhooks — gets you to a green pipeline in an afternoon. Generate your starting Dockerfile from the Docker Builder and customize from there.&lt;/p&gt;

&lt;p&gt;See also: The Only docker-compose.yml Pattern You Need, Cypress + YoBox, Playwright + YoBox.&lt;/p&gt;

&lt;h1&gt;
  
  
  Multi-stage builds for smaller images
&lt;/h1&gt;

&lt;p&gt;For self-hosted runners with bandwidth caps, a multi-stage build keeps only the runtime needed for tests:&lt;/p&gt;

&lt;p&gt;\`dockerfile FROM mcr.microsoft.com/playwright:v1.49.0-jammy AS deps WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci&lt;/p&gt;

&lt;p&gt;FROM mcr.microsoft.com/playwright:v1.49.0-jammy WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENTRYPOINT ["npx", "playwright", "test"] \`&lt;/p&gt;

&lt;h1&gt;
  
  
  Pinning vs floating tags
&lt;/h1&gt;

&lt;p&gt;\v1.49.0-jammy\ is reproducible across years; \latest\ is reproducible for about 12 hours. Pin in CI, float in personal sandboxes.&lt;/p&gt;

&lt;h1&gt;
  
  
  GHA cache versus self-hosted
&lt;/h1&gt;

&lt;p&gt;The GitHub Actions cache is fast but capped per repo. Self-hosted runners with a persistent Docker volume win above ~50 e2e jobs per day. Below that, GHA cache is simpler.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration tips
&lt;/h1&gt;

&lt;p&gt;Most teams adopting containerized e2e do it after they've outgrown a single CI machine. The migration order that works: containerize the test runner first, then add sharding, then move to a self-hosted runner pool once cache pressure shows up.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Webhook Testing: The Complete Guide for 2026</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Fri, 12 Jun 2026 13:36:27 +0000</pubDate>
      <link>https://dev.to/yobox/webhook-testing-the-complete-guide-for-2026-22em</link>
      <guid>https://dev.to/yobox/webhook-testing-the-complete-guide-for-2026-22em</guid>
      <description>&lt;h1&gt;
  
  
  Webhooks are how modern systems talk to each other asynchronously. Stripe sends them. GitHub sends them. Shopify, Slack, Twilio, Postmark, SendGrid, Mailgun, Auth0, Clerk, Supabase, and every payment processor on the planet sends them. If your app integrates with anything, you're either receiving webhooks, sending them, or both.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  And yet testing webhooks is still painful. The traditional setup involves ngrok tunnels, a local server you have to keep running, a way to replay payloads, and a way to debug the headers and signatures. This guide is the complete playbook for testing webhooks in 2026, including the patterns that finally let you do it from a browser tab.
&lt;/h1&gt;

&lt;h1&gt;
  
  
  What a Webhook Actually Is
&lt;/h1&gt;

&lt;p&gt;A webhook is an HTTP POST that a provider sends to a URL you give them when an event happens on their side. That's it. No magic, no special protocol, just HTTP.&lt;/p&gt;

&lt;p&gt;The challenge is everything around the POST:&lt;/p&gt;

&lt;h1&gt;
  
  
  Authentication. Most providers sign the payload with HMAC. You have to verify the signature before trusting the body.
&lt;/h1&gt;

&lt;p&gt;Delivery semantics. At-least-once delivery is the norm — you'll get duplicates. You need idempotency.&lt;br&gt;
Retries. Most providers retry on 5xx for hours. You need fast 2xx returns and a queue.&lt;br&gt;
Order. Webhooks are not ordered. charge.refunded can arrive before charge.succeeded.&lt;br&gt;
Latency. Some providers send within milliseconds; some take minutes.&lt;br&gt;
You need to test all of these, ideally without provisioning real infrastructure.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Old Way: ngrok + a Local Server
&lt;/h1&gt;

&lt;p&gt;Classic flow: 1. ngrok http 3000 2. Copy the random URL. 3. Paste it into the provider's webhook config. 4. Trigger an event in the provider's UI. 5. Watch your terminal for the request. 6. Repeat for every payload variant.&lt;/p&gt;
&lt;h1&gt;
  
  
  Problems: - ngrok URLs rotate on the free tier; reconfigure provider every restart. - You need to actually write a server just to log the payload. - Comparing two payloads means scrolling terminal output. - Sharing a payload with a coworker means screenshots.
&lt;/h1&gt;
&lt;h1&gt;
  
  
  The New Way: Browser-Based Webhook Capture
&lt;/h1&gt;

&lt;p&gt;The YoBox Webhook Tester gives you a unique URL like &lt;a href="https://yobox.dev/webhook/" rel="noopener noreferrer"&gt;https://yobox.dev/webhook/&lt;/a&gt; that captures any HTTP request sent to it and shows you the headers, query, and body in real time, in the browser. No server. No ngrok. Works from CI.&lt;/p&gt;

&lt;p&gt;Use it when:&lt;/p&gt;
&lt;h1&gt;
  
  
  You're integrating with a new provider and want to see what they actually send.
&lt;/h1&gt;

&lt;p&gt;You're debugging why your endpoint isn't responding the way you expect.&lt;br&gt;
You're comparing payload shapes across event types.&lt;br&gt;
You need to share a payload with a coworker (just send the URL).&lt;br&gt;
You're running automated tests that need to assert on webhook delivery.&lt;br&gt;
A Concrete Testing Workflow&lt;br&gt;
Let's say you're integrating Stripe webhooks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a capture URL.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Open YoBox Webhook Tester. You get &lt;a href="https://yobox.dev/webhook/" rel="noopener noreferrer"&gt;https://yobox.dev/webhook/&lt;/a&gt;
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Paste it into Stripe's dashboard.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Subscribe to the events you care about (payment_intent.succeeded, charge.refunded, etc.).
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Trigger events in Stripe's test mode.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Use Stripe's CLI: stripe trigger payment_intent.succeeded.
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Watch the request log fill in.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  You see the full headers (including Stripe-Signature), the query, and the JSON body. Inspect, copy, compare.
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Iterate on your real handler.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Now you know exactly what Stripe sends. Build your handler against the real shape, not the docs (which are sometimes out of date or omit fields).
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Testing Your Own Handler
&lt;/h1&gt;

&lt;p&gt;Once your handler is built, flip the flow. Have your test suite:&lt;/p&gt;
&lt;h1&gt;
  
  
  POST a known payload to your handler.
&lt;/h1&gt;

&lt;p&gt;Assert on the side effects (database row created, downstream API called, etc.).&lt;br&gt;
You can replay captured payloads from the Webhook Tester by copying the body and POSTing it back to your local server. For automated tests, use the YoBox Webhook API — your handler calls out to a downstream service in tests, you point that service at the YoBox capture URL, and your test asserts on what your handler sent.&lt;/p&gt;
&lt;h1&gt;
  
  
  Common Webhook Bugs to Test For
&lt;/h1&gt;

&lt;p&gt;These are the production bugs we see most often:&lt;/p&gt;
&lt;h1&gt;
  
  
  Signature verification skipped in dev
&lt;/h1&gt;

&lt;p&gt;Common pattern: if (process.env.NODE_ENV === 'development') skipVerify(). Then someone forgets to turn it back on. Always test that an unsigned request gets rejected.&lt;/p&gt;
&lt;h1&gt;
  
  
  Body parsed before signature verified
&lt;/h1&gt;

&lt;p&gt;Most signature schemes hash the raw body. If Express body-parser converts it to JSON first, the hash doesn't match. Use express.raw() for webhook routes specifically.&lt;/p&gt;
&lt;h1&gt;
  
  
  Idempotency missing
&lt;/h1&gt;

&lt;p&gt;The same payment_intent.succeeded event arrives twice. Your handler creates two database rows. Always include an idempotency check (typically by the provider's event ID).&lt;/p&gt;
&lt;h1&gt;
  
  
  Slow 2xx response
&lt;/h1&gt;

&lt;p&gt;Your handler takes 30 seconds to do downstream work. The provider times out at 10 and retries. Now you're processing the same event 6 times in parallel. Always return 2xx within 1 second and queue the actual work.&lt;/p&gt;
&lt;h1&gt;
  
  
  Out-of-order events
&lt;/h1&gt;

&lt;p&gt;charge.refunded arrives before charge.succeeded. Your handler crashes because the charge doesn't exist yet. Always fetch from the provider as source of truth; don't trust event order.&lt;/p&gt;
&lt;h1&gt;
  
  
  Test events leaking into production
&lt;/h1&gt;

&lt;p&gt;Stripe test-mode events have livemode: false. Always check it. We've seen production handlers process test events and refund real customers.&lt;/p&gt;
&lt;h1&gt;
  
  
  Webhook Testing in CI
&lt;/h1&gt;

&lt;p&gt;The full pattern:&lt;/p&gt;
&lt;h1&gt;
  
  
  Your CI spins up the app.
&lt;/h1&gt;

&lt;p&gt;A test generates a YoBox webhook URL.&lt;br&gt;
The test configures your app to send downstream webhooks to that URL.&lt;br&gt;
The test triggers the action.&lt;br&gt;
The test polls the YoBox API for the captured request.&lt;br&gt;
The test asserts on the headers and body.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```ts test('order completion fires webhook', async ({ request }) =&amp;gt; { const captureUrl = '&lt;a href="https://yobox.dev/webhook/" rel="noopener noreferrer"&gt;https://yobox.dev/webhook/&lt;/a&gt;' + crypto.randomUUID(); await configureMyApp({ webhookUrl: captureUrl }); await request.post('/orders', { data: { items: [...] } });&lt;/p&gt;

&lt;h1&gt;
  
  
  const captured = await pollWebhook(captureUrl); expect(captured.headers['x-myapp-event']).toBe('order.completed'); expect(captured.body.total).toBeGreaterThan(0); }); ```
&lt;/h1&gt;



&lt;h1&gt;
  
  
  Webhook Security Testing
&lt;/h1&gt;

&lt;p&gt;A security checklist for any webhook handler you write:&lt;/p&gt;

&lt;h1&gt;
  
  
  Signature is verified before any side effects. No DB write, no downstream call until hmac.equals() returns true.
&lt;/h1&gt;

&lt;p&gt;Timing-safe comparison. crypto.timingSafeEqual, not ===.&lt;br&gt;
Timestamp checked for replay. Most providers include a timestamp; reject if older than 5 minutes.&lt;br&gt;
Raw body preserved. Never reparse before verification.&lt;br&gt;
HTTPS only. Don't accept webhooks over HTTP.&lt;br&gt;
Allowlist sender IPs if the provider publishes them. Stripe, GitHub, and Twilio all publish IP ranges.&lt;br&gt;
Webhook Testing for Specific Providers&lt;br&gt;
Quick provider-specific notes:&lt;/p&gt;

&lt;h1&gt;
  
  
  Stripe: use the Stripe CLI's stripe listen + stripe trigger for local. Use YoBox Webhook Tester for staging.
&lt;/h1&gt;

&lt;p&gt;GitHub: the "Redeliver" button in the webhook UI is gold for testing handler changes.&lt;br&gt;
Shopify: test from the admin's webhook log, which can replay any past event.&lt;br&gt;
Twilio: the request inspector shows the full payload Twilio sent — pair with YoBox for archives.&lt;br&gt;
Slack: Slack signing secret has a specific concatenation format; test verification with a known-good payload.&lt;br&gt;
Auth0 / Clerk: both retry aggressively. Make sure your handler is idempotent before testing.&lt;br&gt;
See "Webhook Testing Without ngrok" for a deeper dive on each provider.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Do I still need ngrok for webhooks? Not for inspection. For actually receiving webhooks against code running on your laptop, yes. For just seeing what a provider sends, YoBox Webhook Tester replaces ngrok entirely.&lt;/p&gt;

&lt;p&gt;How do I test signed webhooks? Inspect with YoBox to confirm the signature format, then write a signature-generating helper for your tests that signs known payloads and POSTs them to your handler.&lt;/p&gt;

&lt;p&gt;How long do captured requests stay in YoBox? For the life of the token (typically up to several hours). Save what you need.&lt;/p&gt;

&lt;p&gt;Can I replay a captured webhook to my local server? Yes — copy the body and curl/POST it. The webhook log shows everything you need.&lt;/p&gt;

&lt;p&gt;Can webhook URLs be public? Anyone with the URL can POST to it. That's the point. Use rotation if you're worried about random traffic.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://dev.tourl"&gt;&lt;/a&gt;Bottom Line
&lt;/h1&gt;

&lt;p&gt;Webhook testing in 2026 doesn't require ngrok, a local server, or a tunnel. Use the YoBox Webhook Tester for inspection and capture; build idempotency and signature verification into your handler; test the full async loop in CI by pairing webhook capture with disposable email for end-to-end coverage. Your future self — the one not debugging duplicate charges in production — will thank you.&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>testing</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
