npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cypress-dfe

v0.2.1

Published

Gera NF-e, CT-e e MDF-e para teste automatizado — e corrompe de propósito, de um jeito nomeado, para você provar que seu sistema recusa documento fiscal inválido. Não emite e não transmite.

Readme

cypress-dfe

Gera NF-e (55), CT-e (57) e MDF-e (58) nos seus testes de Cypress — e corrompe de propósito, para você provar que o seu sistema recusa documento fiscal inválido.

npm i -D cypress-dfe

Configurar — duas linhas

cypress.config.js:

const { defineConfig } = require('cypress')
const { registerDFeTasks } = require('cypress-dfe/tasks')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on) {
      registerDFeTasks(on) // ← aqui
    },
  },
})

cypress/support/e2e.js:

import 'cypress-dfe/commands' // ← e aqui

Pronto. Os comandos cy.createNFe(), cy.corruptDFe() e companhia já existem, com autocomplete e tipos — sem precisar de .d.ts nenhum.

Seu primeiro teste

Cole isso em cypress/e2e/fiscal.cy.js e rode:

describe('import de NF-e', () => {
  it('aceita uma nota válida', () => {
    cy.createNFe({ uf: 'PB', CNPJ: '11222333000181' }).then(({ xml, accessKey }) => {
      cy.request({
        method: 'POST',
        url: '/api/notas/import',
        body: { conteudo_xml: xml },
        failOnStatusCode: false,
      }).then(({ status, body }) => {
        expect(status).to.eq(201)
        expect(body.chave).to.eq(accessKey)
      })
    })
  })

  it('recusa uma nota com a chave adulterada', () => {
    cy.createNFe({ uf: 'PB', CNPJ: '11222333000181' })
      .corruptDFe('checkDigit')
      .then(({ xml }) => {
        cy.request({
          method: 'POST',
          url: '/api/notas/import',
          body: { conteudo_xml: xml },
          failOnStatusCode: false,
        })
          .its('status')
          .should('eq', 400)
      })
  })
})

Todo comando devolve { xml, accessKey, model }. O xml é o que você manda para o seu sistema; o accessKey é a chave de 44 dígitos para conferir o que ele gravou.

Gerando o documento

cy.createNFe(opcoes)   // modelo 55
cy.createCTe(opcoes)   // modelo 57
cy.createMDFe(opcoes)  // modelo 58
cy.createDFe({ model: '57', ...opcoes })

uf e CNPJ são obrigatórios. Todo o resto tem padrão.

Identificação

| opção | padrão | | |---|---|---| | uf | — | sigla do emitente: 'PB', 'SP' | | CNPJ | — | 14 dígitos, sem pontuação | | numero | '1' | número do documento | | serie | '1' | série | | dhEmi | '2026-01-01T00:00:00-03:00' | emissão, com fuso obrigatório | | tpAmb | '2' | 2 homologação, 1 produção (gera aviso) | | seed | 0 | semente do sorteio do código numérico | | codigo | sorteado | código numérico de 8 dígitos |

Valores, peso e itens

É o que move cálculo de frete no seu sistema.

| opção | padrão | | |---|---|---| | valor | 10 | total do documento | | itens | um item genérico | lista de produtos | | pesoBruto | 1 | kg | | pesoLiquido | igual ao bruto | kg | | volumes | 1 | contagem; null omite a tag | | valorCarga | igual a valor | CT-e e MDF-e | | produtoPredominante | 'PRODUTO DE TESTE' | CT-e |

cy.createNFe({
  uf: 'PB',
  CNPJ: '11222333000181',
  pesoBruto: 432.25,
  volumes: 7,
  itens: [
    { descricao: 'CAIXA', quantidade: 4, valorUnitario: 25, ncm: '39269090' },
    { descricao: 'PALETE', quantidade: 2, valorUnitario: 100 },
  ],
})
// vNF sai 300.00 — a soma dos itens

Cada item aceita descricao, quantidade, valorUnitario, codigo, ncm, cfop, unidade e ean.

Se você passar itens, o total é a soma deles. Passar também um valor que não bate é erro — um documento que se contradiz seria recusado pelo seu sistema por um motivo que não é o que você queria testar.

Para testar nota que não declara volume:

cy.createNFe({ uf: 'PB', CNPJ: '11222333000181', volumes: null })

Documentos referenciados

O CT-e transporta NF-e; o MDF-e manifesta CT-e. Já sai referenciado, e o totalizador bate:

cy.createMDFe({ uf: 'PB', CNPJ: '11222333000181' })
// <qCTe>1</qCTe> e um <chCTe> com chave válida de CT-e

Para amarrar em documentos seus:

cy.createNFe(emitente).then(({ accessKey }) => {
  cy.createCTe({ ...emitente, chavesNfe: [accessKey] })
})

cy.createMDFe({ ...emitente, chavesCte: [chaveDoCte, outraChave] })

Alterar × corromper

Esta é a distinção que mais importa no pacote.

| | valida? | recalcula a chave? | |---|---|---| | setDFeField(caminho, valor) | sim | sim | | corruptDFe*(...) | não | não |

setDFeField é para mudar algo legítimo. Mexeu em campo que compõe a chave? A chave é refeita, e o Id junto:

cy.createNFe(emitente)
  .setDFeField('ide.nNF', '9999')      // a chave muda sozinha
  .setDFeField('transp.modFrete', '0') // campo que não compõe a chave

Ele recusa valor inválido — ide.dhEmi com mês 13 lança erro citando o campo e a regra. Isso é proposital: recusar é o trabalho dele.

As corrupções fazem o oposto, e é por isso que existem separadas. Nada fica inválido por engano — só por uma chamada que diz isso no nome.

Corrompendo

Um comando aceita qualquer defeito do catálogo:

cy.createNFe(emitente).corruptDFe('checkDigit')

E o catálogo é iterável — é assim que você gera a suíte de rejeição inteira:

import { allCorruptions } from 'cypress-dfe'

describe('meu sistema recusa documento inválido', () => {
  for (const c of allCorruptions('55')) {
    it(`recusa ${c.title}`, () => {
      cy.createNFe(emitente)
        .corruptDFe(c.id)
        .then(({ xml }) => {
          cy.request({ method: 'POST', url: '/api/notas/import', body: { conteudo_xml: xml }, failOnStatusCode: false })
            .its('status')
            .should('eq', 400)
        })
    })
  }
})

São 28 testes na NF-e e 32 no MDF-e, cada um com title, rule e expects — use o expects como mensagem da asserção e o relatório do teste passa a dizer o que se esperava.

Os defeitos disponíveis

Chave de acesso (CORRUPTIONS)

| | | |---|---| | checkDigit | dígito verificador não fecha | | tooShort · tooLong | 43 e 45 dígitos | | nonNumeric | letra no meio | | unknownUF · monthOutOfRange · unknownModel | UF 99, mês 13, modelo 99 | | otherModel · otherIssuer · otherNumber | chave impecável, de outro documento |

Data de emissão (FIELD_CORRUPTIONS)

| | | |---|---| | issuedAtWithZ | sufixo Z — o erro de quem usa toISOString() | | issuedAtWithoutTimezone · issuedAtMonth13 · issuedAtDay32 | | | issuedAtEmpty · issuedAtNotADate | | | issuedAtInFuture · issuedAtInPast | 2099 e 2000 |

Campo ausente (missingFieldCorruptions(modelo))

missingNumber, missingSerie, missingCode, missingIssuedAt, missingModel, missingUF, missingIssuerCNPJ, missingIssuerName, missingAccessKey, missingTotal.

Remover é diferente de esvaziar: <dhEmi/> existe e está vazio; sem a tag, o campo não existe. Seu parser pode tratar cada caso de um jeito.

Transporte (transportCorruptions(modelo)) — a validação cruzada que só um TMS faz:

manifestCountTooHigh, manifestCountTooLow, manifestWithoutDocuments, manifestKeyBrokenCheckDigit, manifestKeyWrongModel, freightTotalMismatch, freightWithoutComponents, cargoKeyBrokenCheckDigit, cargoKeyWrongModel, cargoWithoutDocuments.

Corromper algo que não está no catálogo

cy.createNFe(emitente).corruptDFeRaw('total.ICMSTot.vNF', '-500.00') // valor cru
cy.createNFe(emitente).corruptDFeRemove('emit.xNome')                // remove o campo

O caminho é o do XML a partir do grupo assinado: ide.nNF, emit.CNPJ, total.ICMSTot.vNF, transp.vol. Para atributo, use @: infNFe@Id.

Por que a chave não é recalculada ao corromper

Porque a divergência é o produto. corruptDFeRaw('ide.nNF', '9999') deixa o Id dizendo 1234 e o corpo dizendo 9999 — que é o que um XML adulterado parece, e o que o seu sistema tem que perceber.

Partindo de um XML seu

Se você já tem NF-e de verdade, não precisa gerar:

cy.loadDFe('cypress/fixtures/nfe-real.xml')
  .corruptDFe('otherIssuer')
  .then(({ xml }) => { /* ... */ })

A estrutura vem do seu arquivo; a chave e as corrupções funcionam por cima.

Outros comandos

cy.createNFe(emitente).saveDFe('cypress/downloads/nota.xml') // grava em disco
cy.createNFe(emitente).dfeAccessKey()                        // só a chave
cy.nextDFeNumber('1')                                        // numeração sem colisão

Para upload em tela, transforme o XML em arquivo sem passar pelo disco:

cy.createNFe(emitente).then(({ xml }) => {
  cy.get('input[type=file]').selectFile(
    { contents: Cypress.Buffer.from(xml), fileName: 'nota.xml', mimeType: 'text/xml' },
    { force: true },
  )
})

Por que meu segundo teste dá 409

Porque o gerador é determinístico: mesma entrada, mesma chave. E a chave é única em âmbito nacional — então a segunda importação do mesmo documento é recusada por duplicidade, não pelo defeito que você queria testar.

O sintoma engana: o primeiro teste passa e todos os seguintes falham com um status sem relação com o que está sob teste.

Varie o número:

const BASE = Number(String(Date.now()).slice(-8, -2))
let n = 0
const emitente = () => ({
  uf: 'PB',
  CNPJ: '11222333000181',
  numero: String(BASE + (n += 1)),
})

cy.createNFe(emitente()).corruptDFe('checkDigit')

A base vem do relógio porque o banco do seu sistema persiste entre execuções. Dentro de uma execução só, cy.nextDFeNumber('1') resolve.

O determinismo fica porque é ele que faz um teste que falhou na CI se reproduzir na sua máquina.

Fora do Cypress

O núcleo é puro — sem fs, sem XML, sem Cypress. Roda em Vitest, Jest, Playwright ou script solto:

import { buildAccessKey, validateAccessKey, corruptAccessKey } from 'cypress-dfe'
import { createNFe, corruptDocument } from 'cypress-dfe/node'

const doc = createNFe({ uf: 'PB', CNPJ: '11222333000181', valor: 500 })
doc.accessKey()
doc.toXML()

corruptDocument(doc.toXML(), 'missingNumber').xml
validateAccessKey(doc.accessKey(), { model: '55' }) // { valid: true, errors: [] }

Nome de campo fiscal não é traduzido: nNF, cNF, dhEmi, cUF e tpAmb seguem o MOC. As mensagens de erro são em português e citam campo e regra.

Escopo

  • NF-e (55), CT-e (57) e MDF-e (58) — o recorte é transporte de carga
  • Não emite e não transmite — não fala com a SEFAZ
  • Não calcula tributo — não apura ICMS nem valida CST
  • Não é para produção — padrão é homologação, sem valor fiscal

Estado, sem maquiagem:

| | | |---|---| | Chave de acesso nos três modelos | ✅ com vetor de ouro do MOC do MDF-e | | Corrupção — 4 famílias | ✅ autoverificadas | | Template NF-e 55 | ✅ ordem conferida no leiaute 4.00 | | Template CT-e 57 e MDF-e 58 | ⚠️ ordem do ide ainda não validada contra o XSD | | Assinatura A1 · validação XSD | ⏳ |

Precisa de CT-e fiel ao leiaute hoje? Use cy.loadDFe() com um XML seu.

Mexer no plugin

git clone https://github.com/logangaabriel/cypress-dfe
cd cypress-dfe && npm install
npm run verificar   # tipos + lint + testes com cobertura + auditoria
npm test            # só os testes
npx cypress run     # smoke dos comandos
npm run build

O projeto é governado pelo REGRAS.md, um documento normativo com identificador estável por regra (RD-03, RF-09, ESC-02), citado em code review, commit e mensagem de erro.

Regra escrita de memória é marcada ⚠️ e proibida de implementar antes de conferir no Manual de Orientação do Contribuinte. Já barrou uma fonte errada que circula na web dando a série da chave com 6 dígitos e CT-e como modelo 67 — o modelo do CT-e é 57; 67 é CT-e OS.

Para adicionar uma corrupção: escreva a regra no REGRAS.md, some ao catálogo da família em src/core/, e adicione o teste. A autoverificação faz o resto — corrupção que devolve documento válido lança, em vez de virar teste que passa sem ter testado nada.

Licença

MIT