React & Frontend

Como Usar Playwright com React: E2E, Visual Regression e Component Testing em Produção

19 min de leitura

Como Usar Playwright com React: E2E, Visual Regression e Component Testing em Produção

Entendendo Playwright e sua Integração com React Playwright é uma framework de automação moderna que permite controlar navegadores (Chrome, Firefox, Safari) de forma programática. Diferente de ferramentas legadas, ela foi construída desde o início com foco em confiabilidade, velocidade e suporte a múltiplos navegadores. Quando aplicada em projetos React, o Playwright oferece três tipos de testes complementares: testes end-to-end (E2E), regressão visual e testes de componentes — cada um resolvendo um problema específico. A razão pela qual Playwright se destaca para React é sua capacidade de interagir com o DOM da mesma forma que um usuário real faria, sem depender de detalhes de implementação. Isso significa que seus testes continuam válidos mesmo quando você refatora o código interno do componente, desde que o comportamento visível permaneça o mesmo. Além disso, o Playwright oferece isolamento de contexto (múltiplas abas e janelas em paralelo) e espera inteligente de elementos, reduzindo flakiness — testes que falham aleatoriamente. Instalação e Configuração Inicial Para começar,

<h2>Entendendo Playwright e sua Integração com React</h2>

<p>Playwright é uma framework de automação moderna que permite controlar navegadores (Chrome, Firefox, Safari) de forma programática. Diferente de ferramentas legadas, ela foi construída desde o início com foco em confiabilidade, velocidade e suporte a múltiplos navegadores. Quando aplicada em projetos React, o Playwright oferece três tipos de testes complementares: testes end-to-end (E2E), regressão visual e testes de componentes — cada um resolvendo um problema específico.</p>

<p>A razão pela qual Playwright se destaca para React é sua capacidade de interagir com o DOM da mesma forma que um usuário real faria, sem depender de detalhes de implementação. Isso significa que seus testes continuam válidos mesmo quando você refatora o código interno do componente, desde que o comportamento visível permaneça o mesmo. Além disso, o Playwright oferece isolamento de contexto (múltiplas abas e janelas em paralelo) e espera inteligente de elementos, reduzindo flakiness — testes que falham aleatoriamente.</p>

<h3>Instalação e Configuração Inicial</h3>

<p>Para começar, você precisa instalar o Playwright no seu projeto React. Abra o terminal na raiz do projeto e execute:</p>

<pre><code class="language-bash">npm install -D @playwright/test

npx playwright install</code></pre>

<p>O comando <code>playwright install</code> baixa os navegadores necessários. Isso é crítico — sem essa etapa, os testes não funcionarão.</p>

<p>Agora crie um arquivo de configuração <code>playwright.config.ts</code> na raiz do seu projeto:</p>

<pre><code class="language-typescript">import { defineConfig, devices } from &#039;@playwright/test&#039;;

export default defineConfig({

testDir: &#039;./e2e&#039;,

fullyParallel: true,

forbidOnly: !!process.env.CI,

retries: process.env.CI ? 2 : 0,

workers: process.env.CI ? 1 : undefined,

reporter: &#039;html&#039;,

use: {

baseURL: &#039;http://localhost:3000&#039;,

trace: &#039;on-first-retry&#039;,

},

webServer: {

command: &#039;npm run dev&#039;,

url: &#039;http://localhost:3000&#039;,

reuseExistingServer: !process.env.CI,

},

projects: [

{

name: &#039;chromium&#039;,

use: { ...devices[&#039;Desktop Chrome&#039;] },

},

{

name: &#039;firefox&#039;,

use: { ...devices[&#039;Desktop Firefox&#039;] },

},

{

name: &#039;webkit&#039;,

use: { ...devices[&#039;Desktop Safari&#039;] },

},

],

});</code></pre>

<p>Este arquivo configura três coisas essenciais: a URL base da aplicação, o servidor de desenvolvimento (que será iniciado automaticamente), e os navegadores em que os testes rodará. A opção <code>baseURL</code> economiza digitação — você não precisa escrever <code>http://localhost:3000</code> em cada teste.</p>

<h2>Testes End-to-End (E2E) com Playwright</h2>

<p>Testes E2E simulam fluxos reais de usuários. Você clica em botões, preenche formulários, navega entre páginas e valida que o resultado corresponde às expectativas. Diferente de testes unitários, E2E não se importa com detalhes internos — apenas com o que o usuário vê e interage.</p>

<h3>Estruturando seus Primeiros Testes</h3>

<p>Crie um diretório <code>e2e</code> na raiz do projeto. Lá, você colocará seus testes. Vamos começar com um exemplo simples: uma página de login.</p>

<p>Suponha que sua aplicação React tenha uma página <code>/login</code> com um formulário básico:</p>

<pre><code class="language-typescript">// src/pages/Login.tsx

import React, { useState } from &#039;react&#039;;

import { useNavigate } from &#039;react-router-dom&#039;;

export function Login() {

const [email, setEmail] = useState(&#039;&#039;);

const [password, setPassword] = useState(&#039;&#039;);

const [error, setError] = useState(&#039;&#039;);

const navigate = useNavigate();

const handleSubmit = async (e: React.FormEvent) =&gt; {

e.preventDefault();

if (!email || !password) {

setError(&#039;Email e senha são obrigatórios&#039;);

return;

}

// Simula chamada à API

if (email === &#039;user@example.com&#039; &amp;&amp; password === &#039;password123&#039;) {

localStorage.setItem(&#039;token&#039;, &#039;fake-token&#039;);

navigate(&#039;/dashboard&#039;);

} else {

setError(&#039;Credenciais inválidas&#039;);

}

};

return (

&lt;form onSubmit={handleSubmit}&gt;

&lt;input

type=&quot;email&quot;

placeholder=&quot;Email&quot;

value={email}

onChange={(e) =&gt; setEmail(e.target.value)}

data-testid=&quot;email-input&quot;

/&gt;

&lt;input

type=&quot;password&quot;

placeholder=&quot;Senha&quot;

value={password}

onChange={(e) =&gt; setPassword(e.target.value)}

data-testid=&quot;password-input&quot;

/&gt;

&lt;button type=&quot;submit&quot; data-testid=&quot;submit-button&quot;&gt;

Entrar

&lt;/button&gt;

{error &amp;&amp; &lt;div data-testid=&quot;error-message&quot;&gt;{error}&lt;/div&gt;}

&lt;/form&gt;

);

}</code></pre>

<p>Agora, crie o teste E2E em <code>e2e/login.spec.ts</code>:</p>

<pre><code class="language-typescript">import { test, expect } from &#039;@playwright/test&#039;;

test.describe(&#039;Login Flow&#039;, () =&gt; {

test(&#039;deve exibir erro quando credenciais inválidas&#039;, async ({ page }) =&gt; {

// Navega até a página de login

await page.goto(&#039;/login&#039;);

// Preenche os campos com dados inválidos

await page.fill(&#039;[data-testid=&quot;email-input&quot;]&#039;, &#039;wrong@example.com&#039;);

await page.fill(&#039;[data-testid=&quot;password-input&quot;]&#039;, &#039;wrongpassword&#039;);

// Clica no botão de submit

await page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

// Valida que a mensagem de erro apareceu

const errorMessage = page.locator(&#039;[data-testid=&quot;error-message&quot;]&#039;);

await expect(errorMessage).toContainText(&#039;Credenciais inválidas&#039;);

});

test(&#039;deve fazer login com sucesso e redirecionar&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Preenche com credenciais válidas

await page.fill(&#039;[data-testid=&quot;email-input&quot;]&#039;, &#039;user@example.com&#039;);

await page.fill(&#039;[data-testid=&quot;password-input&quot;]&#039;, &#039;password123&#039;);

await page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

// Valida redirecionamento

await expect(page).toHaveURL(&#039;/dashboard&#039;);

// Valida que o token foi armazenado

const token = await page.evaluate(() =&gt; localStorage.getItem(&#039;token&#039;));

expect(token).toBe(&#039;fake-token&#039;);

});

test(&#039;deve desabilitar submit se campos vazios&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Não preenche nada, apenas clica

await page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

// Valida a mensagem de erro

const errorMessage = page.locator(&#039;[data-testid=&quot;error-message&quot;]&#039;);

await expect(errorMessage).toContainText(&#039;obrigatórios&#039;);

});

});</code></pre>

<p>Note que usamos <code>data-testid</code> para identificar elementos. Isso é uma boa prática — você não depende de classes CSS que mudam frequentemente. O Playwright aguarda automaticamente que o elemento esteja visível antes de interagir, reduzindo falsos negativos.</p>

<h3>Usando Locadores Eficientemente</h3>

<p>Playwright oferece várias formas de localizar elementos. As mais robustas são:</p>

<pre><code class="language-typescript">// data-testid é a mais confiável

page.locator(&#039;[data-testid=&quot;button&quot;]&#039;)

// CSS selectors funcionam bem

page.locator(&#039;button.primary&#039;)

// Role-based (acessibilidade) é muito bom

page.getByRole(&#039;button&#039;, { name: &#039;Submit&#039; })

// Label

page.getByLabel(&#039;Email&#039;)

// Placeholder

page.getByPlaceholder(&#039;Digite seu email&#039;)</code></pre>

<p>O <code>getByRole</code> é especialmente poderoso porque força você a criar componentes acessíveis. Se um botão não tem role correto, o teste falha — e isso é uma vitória para acessibilidade:</p>

<pre><code class="language-typescript">// Bom: uso de role semanticamente correto

await page.getByRole(&#039;button&#039;, { name: /entrar/i }).click();

await page.getByRole(&#039;textbox&#039;, { name: &#039;Email&#039; }).fill(&#039;test@example.com&#039;);

// Evite quando possível: muito específico de implementação

await page.locator(&#039;div.login-form &gt; input:nth-child(1)&#039;)</code></pre>

<h2>Testes de Regressão Visual</h2>

<p>Regressão visual detecta mudanças não intencionais no design. Alguém muda uma cor CSS, e o layout quebra sutilmente — testes visuais pegam isso. Playwright captura screenshots em momentos críticos e compara com screenshots de referência.</p>

<h3>Como Funciona Visual Regression</h3>

<p>O Playwright tira uma screenshot e a compara pixel-por-pixel com uma imagem armazenada. Na primeira execução, cria a imagem de referência. Nas execuções seguintes, detecta diferenças. Se houver mudanças, gera um relatório visual mostrando o antes, depois e a diferença.</p>

<pre><code class="language-typescript">import { test, expect } from &#039;@playwright/test&#039;;

test.describe(&#039;Visual Regression&#039;, () =&gt; {

test(&#039;página de login deve estar visualmente correta&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Aguarda a página carregar completamente

await page.waitForLoadState(&#039;networkidle&#039;);

// Tira screenshot da página inteira

await expect(page).toHaveScreenshot(&#039;login-page.png&#039;);

});

test(&#039;componente de formulário deve renderizar corretamente&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Tira screenshot apenas de um elemento específico

const form = page.locator(&#039;form&#039;);

await expect(form).toHaveScreenshot(&#039;login-form.png&#039;);

});

test(&#039;estado de hover deve estar correto&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

const button = page.locator(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

// Hover no botão

await button.hover();

// Captura screenshot com o estado de hover

await expect(button).toHaveScreenshot(&#039;button-hover.png&#039;);

});

});</code></pre>

<p>Execute os testes para gerar as imagens de referência:</p>

<pre><code class="language-bash">npx playwright test --update-snapshots</code></pre>

<p>Nas execuções posteriores, qualquer diferença será detectada:</p>

<pre><code class="language-bash">npx playwright test</code></pre>

<p>Se houver diferenças, o Playwright gera um relatório HTML mostrando lado-a-lado:</p>

<pre><code class="language-bash">npx playwright show-report</code></pre>

<h3>Gerenciando Imagens de Referência</h3>

<p>As imagens são armazenadas em <code>e2e/{test-name}.spec.ts-snapshots/</code>. Commit essas imagens no Git — são parte do seu teste. Quando a mudança é intencional (novo design), regenere com <code>--update-snapshots</code>.</p>

<p>Um problema comum: testes visuais são sensíveis a detalhes do ambiente (fonte renderizada, anti-aliasing). Para reduzir falsos positivos, configure tolerância:</p>

<pre><code class="language-typescript">await expect(page).toHaveScreenshot(&#039;login-page.png&#039;, {

maxDiffPixels: 100, // Permite até 100 pixels diferentes

threshold: 0.2, // Permite até 20% de diferença

});</code></pre>

<h2>Testes de Componentes com Playwright</h2>

<p>Component testing é diferente de E2E. Você testa um componente React isoladamente, sem o servidor web completo. É como um teste unitário visual — rápido, focado e determinístico.</p>

<h3>Configurando Component Testing</h3>

<p>Primeiro, instale o adapter do Playwright para React:</p>

<pre><code class="language-bash">npm install -D @playwright/experimental-ct-react</code></pre>

<p>Crie um arquivo <code>playwright-ct.config.ts</code>:</p>

<pre><code class="language-typescript">import { defineConfig, devices } from &#039;@playwright/experimental-ct-react&#039;;

export default defineConfig({

testDir: &#039;./src/components&#039;,

fullyParallel: true,

forbidOnly: !!process.env.CI,

retries: process.env.CI ? 2 : 0,

workers: process.env.CI ? 1 : undefined,

reporter: &#039;html&#039;,

use: {

trace: &#039;on-first-retry&#039;,

screenshot: &#039;only-on-failure&#039;,

},

webServer: undefined,

projects: [

{

name: &#039;chromium&#039;,

use: { ...devices[&#039;Desktop Chrome&#039;] },

},

{

name: &#039;firefox&#039;,

use: { ...devices[&#039;Desktop Firefox&#039;] },

},

],

});</code></pre>

<h3>Testando Componentes Isoladamente</h3>

<p>Digamos que você tenha um componente Button reutilizável:</p>

<pre><code class="language-typescript">// src/components/Button.tsx

import React from &#039;react&#039;;

import &#039;./Button.css&#039;;

interface ButtonProps {

onClick?: () =&gt; void;

disabled?: boolean;

children: React.ReactNode;

variant?: &#039;primary&#039; | &#039;secondary&#039;;

}

export function Button({

onClick,

disabled = false,

children,

variant = &#039;primary&#039;,

}: ButtonProps) {

return (

&lt;button

onClick={onClick}

disabled={disabled}

className={button button--${variant}}

data-testid=&quot;button&quot;

&gt;

{children}

&lt;/button&gt;

);

}</code></pre>

<p>Crie o teste de componente em <code>src/components/Button.spec.tsx</code>:</p>

<pre><code class="language-typescript">import { test, expect } from &#039;@playwright/experimental-ct-react&#039;;

import { Button } from &#039;./Button&#039;;

test.describe(&#039;Button Component&#039;, () =&gt; {

test(&#039;deve renderizar com o texto correto&#039;, async ({ mount }) =&gt; {

const component = await mount(&lt;Button&gt;Clique aqui&lt;/Button&gt;);

const button = component.locator(&#039;[data-testid=&quot;button&quot;]&#039;);

await expect(button).toContainText(&#039;Clique aqui&#039;);

});

test(&#039;deve chamar onClick quando clicado&#039;, async ({ mount }) =&gt; {

let clicked = false;

const component = await mount(

&lt;Button onClick={() =&gt; { clicked = true; }}&gt;

Clique

&lt;/Button&gt;

);

const button = component.locator(&#039;[data-testid=&quot;button&quot;]&#039;);

await button.click();

expect(clicked).toBe(true);

});

test(&#039;deve estar desabilitado quando prop disabled é true&#039;, async ({ mount }) =&gt; {

const component = await mount(

&lt;Button disabled onClick={() =&gt; {}}&gt;

Desabilitado

&lt;/Button&gt;

);

const button = component.locator(&#039;[data-testid=&quot;button&quot;]&#039;);

await expect(button).toBeDisabled();

});

test(&#039;deve aplicar classe de variante corretamente&#039;, async ({ mount }) =&gt; {

const component = await mount(

&lt;Button variant=&quot;secondary&quot;&gt;Secondary&lt;/Button&gt;

);

const button = component.locator(&#039;[data-testid=&quot;button&quot;]&#039;);

await expect(button).toHaveClass(/button--secondary/);

});

test(&#039;visual: botão primário deve estar correto&#039;, async ({ mount }) =&gt; {

const component = await mount(&lt;Button variant=&quot;primary&quot;&gt;Primary&lt;/Button&gt;);

await expect(component).toHaveScreenshot(&#039;button-primary.png&#039;);

});

test(&#039;visual: botão desabilitado deve estar correto&#039;, async ({ mount }) =&gt; {

const component = await mount(

&lt;Button disabled variant=&quot;primary&quot;&gt;Disabled&lt;/Button&gt;

);

await expect(component).toHaveScreenshot(&#039;button-disabled.png&#039;);

});

});</code></pre>

<p>Note a diferença: em component testing, você usa <code>mount()</code> em vez de <code>page.goto()</code>. O componente é renderizado em isolamento, sem toda a aplicação. Isso torna os testes mais rápidos.</p>

<h3>Testando Componentes com Context e Hooks</h3>

<p>Se seu componente depende de Context ou hooks customizados, você pode envolver o componente em providers:</p>

<pre><code class="language-typescript">// src/components/UserCard.spec.tsx

import { test, expect } from &#039;@playwright/experimental-ct-react&#039;;

import { UserCard } from &#039;./UserCard&#039;;

import { UserProvider } from &#039;../contexts/UserContext&#039;;

test(&#039;deve exibir informações do usuário do context&#039;, async ({ mount }) =&gt; {

const component = await mount(

&lt;UserProvider initialUser={{ name: &#039;João&#039;, email: &#039;joao@example.com&#039; }}&gt;

&lt;UserCard /&gt;

&lt;/UserProvider&gt;

);

await expect(component.locator(&#039;text=João&#039;)).toBeVisible();

await expect(component.locator(&#039;text=joao@example.com&#039;)).toBeVisible();

});</code></pre>

<h2>Boas Práticas e Padrões Avançados</h2>

<h3>Executando Testes em CI/CD</h3>

<p>Em pipelines (GitHub Actions, GitLab CI, etc.), configure o Playwright para rodar em modo headless com retry:</p>

<pre><code class="language-yaml"># .github/workflows/test.yml

name: Tests

on: [push, pull_request]

jobs:

test:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: actions/setup-node@v4

with:

node-version: &#039;18&#039;

  • run: npm ci
  • run: npx playwright install --with-deps
  • run: npm run test:e2e
  • run: npm run test:component
  • uses: actions/upload-artifact@v4

if: failure()

with:

name: playwright-report

path: playwright-report/</code></pre>

<h3>Padrão Page Object</h3>

<p>Em E2E, use Page Objects para abstrair seletores e ações. Isso reduz duplicação e facilita manutenção:</p>

<pre><code class="language-typescript">// e2e/pages/LoginPage.ts

import { Page } from &#039;@playwright/test&#039;;

export class LoginPage {

constructor(private page: Page) {}

async goto() {

await this.page.goto(&#039;/login&#039;);

}

async fillEmail(email: string) {

await this.page.fill(&#039;[data-testid=&quot;email-input&quot;]&#039;, email);

}

async fillPassword(password: string) {

await this.page.fill(&#039;[data-testid=&quot;password-input&quot;]&#039;, password);

}

async clickSubmit() {

await this.page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

}

async getErrorMessage() {

return this.page.locator(&#039;[data-testid=&quot;error-message&quot;]&#039;).textContent();

}

}

// e2e/login.spec.ts — muito mais limpo

import { test, expect } from &#039;@playwright/test&#039;;

import { LoginPage } from &#039;./pages/LoginPage&#039;;

test(&#039;login com credenciais inválidas&#039;, async ({ page }) =&gt; {

const loginPage = new LoginPage(page);

await loginPage.goto();

await loginPage.fillEmail(&#039;wrong@example.com&#039;);

await loginPage.fillPassword(&#039;wrong&#039;);

await loginPage.clickSubmit();

const errorMessage = await loginPage.getErrorMessage();

expect(errorMessage).toContain(&#039;inválidas&#039;);

});</code></pre>

<h3>Testando Requisições HTTP</h3>

<p>O Playwright intercepta requisições HTTP, permitindo validar chamadas à API:</p>

<pre><code class="language-typescript">test(&#039;deve enviar dados corretos para API ao fazer login&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Aguarda por uma requisição POST e valida seu payload

const requestPromise = page.waitForRequest(

request =&gt; request.url().includes(&#039;/api/login&#039;) &amp;&amp; request.method() === &#039;POST&#039;

);

await page.fill(&#039;[data-testid=&quot;email-input&quot;]&#039;, &#039;user@example.com&#039;);

await page.fill(&#039;[data-testid=&quot;password-input&quot;]&#039;, &#039;password123&#039;);

await page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

const request = await requestPromise;

const postData = request.postDataJSON();

expect(postData).toEqual({

email: &#039;user@example.com&#039;,

password: &#039;password123&#039;,

});

});</code></pre>

<p>Também é possível mockar respostas:</p>

<pre><code class="language-typescript">test(&#039;deve exibir erro quando API falha&#039;, async ({ page }) =&gt; {

await page.goto(&#039;/login&#039;);

// Mocka a resposta da API

await page.route(&#039;**/api/login&#039;, route =&gt; {

route.abort(&#039;failed&#039;);

});

await page.fill(&#039;[data-testid=&quot;email-input&quot;]&#039;, &#039;user@example.com&#039;);

await page.fill(&#039;[data-testid=&quot;password-input&quot;]&#039;, &#039;password123&#039;);

await page.click(&#039;[data-testid=&quot;submit-button&quot;]&#039;);

const errorMessage = page.locator(&#039;[data-testid=&quot;error-message&quot;]&#039;);

await expect(errorMessage).toContainText(&#039;Erro ao conectar&#039;);

});</code></pre>

<h2>Conclusão</h2>

<p>Playwright é extraordinariamente poderoso para testar aplicações React em múltiplas dimensões. Primeiro, testes E2E garantem que fluxos completos funcionam do ponto de vista do usuário — você valida comportamentos reais, não implementações. Segundo, testes de regressão visual pegam mudanças acidentais no design, coisa que testes unitários jamais fariam. Terceiro, testes de componentes oferecem rapidez e isolamento, testando peças individualmente antes de juntá-las.</p>

<p>O segredo está em combinar estrategicamente: use E2E para jornadas críticas do usuário (login, checkout), visual regression para componentes visuais importantes, e component tests para lógica de componentes reutilizáveis. Essa tríade, bem executada, oferece cobertura confiável sem overhead excessivo. E não esqueça: data-testid, Page Objects e retry policies inteligentes transformam testes flaky em suites robustas.</p>

<h2>Referências</h2>

<ol>

<li><strong>Playwright Official Documentation</strong> — https://playwright.dev/</li>

<li><strong>Playwright Component Testing Guide</strong> — https://playwright.dev/docs/test-components</li>

<li><strong>React Testing Best Practices with Playwright</strong> — https://playwright.dev/docs/frameworks</li>

<li><strong>W3C WebDriver Protocol</strong> — https://www.w3.org/TR/webdriver/</li>

<li><strong>Testing Library — Accessibility-first queries</strong> — https://testing-library.com/</li>

</ol>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Boas Práticas de Zustand em Profundidade: Slices, Middleware e Persist para Times Ágeis
Boas Práticas de Zustand em Profundidade: Slices, Middleware e Persist para Times Ágeis

Introdução ao Zustand e Sua Arquitetura Zustand é uma biblioteca de gerenciam...

Boas Práticas de Zod com React Hook Form: Schemas Complexos e Erros Customizados para Times Ágeis
Boas Práticas de Zod com React Hook Form: Schemas Complexos e Erros Customizados para Times Ágeis

Introdução: Por Que Zod com React Hook Form? A validação de formulários é um...

MSW em React: Mock Service Worker para Testes e Desenvolvimento na Prática
MSW em React: Mock Service Worker para Testes e Desenvolvimento na Prática

O que é MSW (Mock Service Worker)? Mock Service Worker é uma biblioteca JavaS...