Cucumber-js: Suporte a módulo JS nativo em definições de etapas e código de suporte

Criado em 3 abr. 2020  ·  13Comentários  ·  Fonte: cucumber/cucumber-js

Problema

Quando tento executar Cucumber.js com uma definição de etapa definida em um módulo ECMAScript nativo (conforme documentado aqui ), a tentativa falha com um aviso de que estou usando require() do CommonJS em um módulo ES.

Versão Cucumber.js: 6.0.5
Versão do nó: 13.8.0

Passos para reproduzir

  1. Configure um diretório de pacote NPM básico usando npm init e npm i cucumber

    • Defina "type": "module" no arquivo package.json para garantir que os arquivos JS serão tratados como módulos nativos

  2. Crie um arquivo de recurso básico, features/mjs.feature :

    Feature: Native JS Modules
    
        Scenario: Load a native JS module step definition
            Given I have 42 cucumbers in my belly
    
  3. Crie um arquivo básico de definição de etapa, features/step_definitions.js :

    import { Given } from "cucumber";
    
    Given("I have {int} cucumbers in my belly", function (cucumberCount) {
        console.log("Step parsed.");
    });
    
  4. Tentativa de executar o Cucumber:

    $ ./node_modules/.bin/cucumber-js
    

Resultado esperado

O módulo de definição de etapa deve ser carregado e usado para analisar a etapa.

Resultado atual

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /some/path/cucumbertest/features/step_definitions.js
require() of ES modules is not supported.
require() of /some/path/cucumbertest/features/step_definitions.js from /some/path/cucumbertest/node_modules/cucumber/lib/cli/index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename step_definitions.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /some/path/cucumbertest/package.json.

    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:13)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Module.require (internal/modules/cjs/loader.js:1040:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at /some/path/cucumbertest/node_modules/cucumber/lib/cli/index.js:119:42
    at Array.forEach (<anonymous>)
    at Cli.getSupportCodeLibrary (/some/path/cucumbertest/node_modules/cucumber/lib/cli/index.js:119:22)
    at Cli.run (/some/path/cucumbertest/node_modules/cucumber/lib/cli/index.js:141:37)
    at async Object.run [as default] (/some/path/cucumbertest/node_modules/cucumber/lib/cli/run.js:30:14)

Fechando

Pelo que vale a pena, eu realmente aprecio o trabalho que a equipe Cucumber.js fez neste projeto, tem sido um grande ativo para mim e para minha empresa. Minha equipe investiu algum tempo na construção de alguns componentes de aplicativos compartilhados em módulos JS nativos e, até recentemente, eu estava assumindo (com base na sintaxe import da documentação mais recente) que essas coisas funcionariam com Cucumber quando nós conseguiu integrar com nossa estrutura de teste. No entanto, não parece ser esse o caso. Como o componente compartilhado já está escrito e integrado em outras partes de nosso aplicativo, estou relutante em usar apenas definições de etapas e código de suporte escrito em CommonJS devido aos desafios de interoperabilidade.

Outras coisas que tentei fazer funcionar incluíam ...

  • Dar aos arquivos de módulo nativo .mjs extensões, de acordo com a convenção Node.js (isso fez com que eles fossem ignorados pela lógica de carregamento automático do Cucumber, e usando --require para carregar explicitamente o módulo de definição de etapas jogou Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
  • Envolvendo os módulos nativos em módulos CJS que usam a função dinâmica import() ; o problema é que essa abordagem é assíncrona e, mesmo que a promessa de importação seja exportada pelo módulo CJS, o Cucumber não parece esperar que a promessa seja resolvida antes de prosseguir, porque a etapa foi marcada como indefinida (sugerindo que a definição da etapa no módulo nativo ainda não foi registrado).

Se estiver ao meu alcance, ficaria feliz em enviar um PR para ajudar a resolver esse problema, mas não estou familiarizado com o funcionamento interno do Cucumber.js e agradeceria se alguém pudesse me indicar a direção certa.

accepted enhancement

Comentários muito úteis

Eu uso a extensão .js e "type":"module" pois espero que isso se torne a norma a longo prazo.

Todos 13 comentários

Eu gostaria de fazer o cucumber-js trabalhar com o ESM. Já tínhamos algumas outras questões abertas sobre isso.

Poderíamos fazer uma opção CLI (ou usar alguma outra maneira de procurar quando utilizá-la) que faz o cucumber-js usar import vez de exigir e esperar pela promessa retornada.

Dado o exemplo que você adicionou, poderíamos adicionar um caso de teste especificamente para isso e trabalhar para torná-lo aprovado. Experimentando a troca de exigir a importação e aguardando a promessa, encontrei outro erro:

⋊> ~/p/test ./node_modules/.bin/cucumber-js                                21:05:22
(node:7422) ExperimentalWarning: The ESM module loader is experimental.
file:///test/features/steps.js:1
import { Given } from "cucumber";
         ^^^^^
SyntaxError: The requested module 'cucumber' does not provide an export named 'Given'
    at ModuleJob._instantiate (internal/modules/esm/module_job.js:92:21)
    at async ModuleJob.run (internal/modules/esm/module_job.js:107:20)
    at async Loader.import (internal/modules/esm/loader.js:176:24)
    at async Promise.all (index 0)
    at async Cli.getSupportCodeLibrary (/test/node_modules/cucumber/lib/cli/index.js:119:5)
    at async Cli.run (/test/node_modules/cucumber/lib/cli/index.js:141:32)
    at async Object.run [as default] (/test/node_modules/cucumber/lib/cli/run.js:30:14)

Peguei o exemplo (ligeiramente modificado) da documentação de definição de etapa, mas você está certo, não funcionará como está escrito. Como Cucumber.js é um módulo CommonJS, ele fornece apenas uma exportação padrão. No curto prazo, eu provavelmente apenas importaria o padrão do módulo e, em seguida, acessaria os métodos Dado / Quando / Então a partir dele:

import cucumber from "cucumber";

cucumber.Given(...);

A longo prazo, seria bom se o Cucumber tivesse pontos de entrada separados para importar e exigir, como mostrado aqui .

Obrigado por olhar para isso. Pelo que vale a pena, uma opção CLI para dizer ao Cucumber para importar em vez de exigir seria adequado para mim. Também seria ótimo se Cucumber procurasse arquivos de suporte com extensões .cjs e .mjs e os tratasse automaticamente como CommonJS e módulos nativos, respectivamente.

EDITAR: só quero reiterar que estou feliz em ajudar com tudo isso, se puder; apenas me aponte na direção certa.

Eu provavelmente apenas importaria o padrão do módulo e, em seguida, acessaria os métodos Dado / Quando / Então a partir dele:
importe pepino de "pepino";
pepino.Dado (...);

Isso não funciona. Eu recebo a mesma mensagem.
Eu tenho "type": "module" no package.json e o resto do meu projeto está usando módulos, então mudar isso não é uma opção.

Se eu usar um destes:

import cucumber from '@cucumber/cucumber';
// OR
import { When } from '@cucumber/cucumber';

Recebo a mesma mensagem de erro da postagem original.
Se eu mudar para

const cucumber = require('@cucumber/cucumber');

Então eu recebo esta mensagem:

Em vez disso, renomeie /some/path/cucumbertest/features/step_definitions.js para terminar em .cjs, altere o código necessário para usar import () ou remova "type": "module" de project / package.json.

Se eu renomear step_definitions.js para _either_ step_definitions.cjs ou step_definitions.mjs , ele simplesmente será ignorado e recebo mensagens genéricas do pepino dizendo para criar stubs para minhas etapas:

UUUUUU

Failures:

1) Scenario: Empty Payload # spec\cucumber\features\users\create\main.feature:4
   ? When the client creates a POST request to /users
       Undefined. Implement with the following snippet:

         When('the client creates a POST request to \/users', function () {
           // Write code here that turns the phrase above into concrete actions
           return 'pending';
         });

... etc

Eu recebo exatamente as mesmas mensagens se excluir o arquivo completamente, então ele está claramente sendo ignorado.

Solução (mais ou menos)

No final, a única maneira de fazer o pepino funcionar em um projeto com "type": "module" é criar um specpackage.json separado sem "type": "module" e instalar o pepino em spec/node_modules .

Até agora, parece estar funcionando. Vamos ver se ele ainda está funcionando depois de criar alguns testes reais.

Para os mantenedores: esta é a primeira vez que uso pepino, e até agora estou gostando. No entanto, isso realmente prejudicou meu progresso. Em vez de passar a tarde aprendendo pepino, passei-o depurando importações. Eu sei como é irritante tentar criar uma biblioteca que suporte as importações ES6 e CJS, então você tem minha simpatia.

Uma possível solução:
É possível publicar compilações CJS e ES6 de uma vez se você gerar um segundo arquivo dist:

dist / cucumber.js
dist / cucumber.module.js

Em seguida, adicione esta linha ao package.json:

"módulo": "dist / cucumber.module.js"

Isso deve permitir que o nó resolva as importações ES6.

tem que haver uma maneira melhor de fazer isso. Eu também estou tendo esse problema e, como as importações não podem ser usadas com o commonJS, eu teria que usar
https://nodejs.org/api/esm.html#esm_import_expressions

em todos os lugares...

existe uma maneira de o Cucumber ter um sinalizador para nos ajudar a usar o ES?

Uma rápida para as pessoas interessadas nisso: ao usar o ESM em um projeto com pepino, você:

  • Use a extensão .js e "type":"module"
  • Use a extensão .mjs
  • Algo mais?

Obrigado!

Eu tendo a seguir a convenção de usar extensões .mjs e .cjs para todos os meus arquivos JavaScript para evitar ambigüidade, e seria minha preferência fazer isso também neste caso. No entanto, não seria nenhum grande inconveniente definir “tipo”: “módulo” no nível do projeto e deixá-los como arquivos .js se essa for a melhor solução para mais usuários.

Eu uso a extensão .js e "type":"module" pois espero que isso se torne a norma a longo prazo.

Obrigado pelo feedback @adamlacoste @looeee

@davidjgoss o # 1589 sendo mesclado é o suficiente para que esse problema seja

Obrigado pela cotovelada @ aurelien-reeves!

A versão 7.2.0 de @cucumber/cucumber agora está em npm, incluindo suporte experimental para ESM conforme descrito nos documentos aqui:
https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#es-modules-experimental-nodejs-12

Se os interessados ​​pudessem experimentar e relatar o que você achou, isso seria ótimo. Eu coloquei um projeto de exemplo super minimalista aqui:
https://github.com/davidjgoss/cucumber-esm-example

Fico feliz em manter esse problema em aberto por um tempo, apenas para pegar o feedback inicial em um só lugar.

Portanto, essa mudança causou um problema com estruturas e formatadores de terceiros que require certas coisas diretamente (em oposição ao ponto de entrada). Eu lancei o 7.2.1, que é basicamente uma reversão para o 7.1.0 para desbloquear essas pessoas, e irei investigar a causa e ver se podemos evitá-lo enquanto ainda oferecemos suporte ao ESM. Enquanto isso, se isso não afeta você, o 7.2.0 ainda está lá para experimentar.

Tenho tentado dar uma chance a v7.2.0 junto com a bandeira --esm . eu uso o testdouble nos testes do pacote que estou tentando converter para ESM.

para usar td.replaceEsm , um carregador deve ser usado como --loader=testdouble . quando tento fornecer o carregador diretamente para o pepino cli, recebo o seguinte erro:

> cucumber-js test/integration --esm --loader=testdouble

error: unknown option '--loader=testdouble'

como essa opção não estava disponível, tentei NODE_OPTIONS :

> NODE_OPTIONS='--loader=testdouble' cucumber-js test/integration --esm

(node:62231) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /path/to/project/node_modules/@cucumber/cucumber/bin/cucumber-js
    at defaultGetFormat (internal/modules/esm/get_format.js:71:15)
    at getFormat (file:///path/to/project/node_modules/quibble/lib/quibble.mjs:65:12)
    at Loader.getFormat (internal/modules/esm/loader.js:104:42)
    at Loader.getModuleJob (internal/modules/esm/loader.js:242:31)
    at async Loader.import (internal/modules/esm/loader.js:176:17)
    at async Object.loadESM (internal/process/esm_loader.js:68:5) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

para referência, isso era contra o nó v14.17.0

seria bom se isso funcionasse com a abordagem NODE_OPTIONS , mas idealmente a opção --loader seria suportada diretamente pelo cli como algumas estruturas de teste começaram a suportar. considere essas abordagens com sua próxima versão que aborda o suporte ESM :)

existe uma abordagem para definir um carregador que você esperaria que funcionasse com v7.2.0 ?

Esta página foi útil?
0 / 5 - 0 avaliações