Cucumber-js: Soporte de módulo JS nativo en definiciones de pasos y código de soporte

Creado en 3 abr. 2020  ·  13Comentarios  ·  Fuente: cucumber/cucumber-js

Problema

Cuando intento ejecutar Cucumber.js con una definición de paso definida en un módulo ECMAScript nativo (como se documenta aquí ), el intento falla con una advertencia de que estoy usando require() CommonJS en un módulo ES.

Versión de Cucumber.js: 6.0.5
Versión de nodo: 13.8.0

pasos para reproducir

  1. Configure un directorio de paquetes NPM básico usando npm init y npm i cucumber

    • Establezca "type": "module" en el archivo package.json para asegurarse de que los archivos JS se traten como módulos nativos

  2. Cree un archivo de características básicas, features/mjs.feature :

    Feature: Native JS Modules
    
        Scenario: Load a native JS module step definition
            Given I have 42 cucumbers in my belly
    
  3. Cree un archivo de definición de pasos básico, features/step_definitions.js :

    import { Given } from "cucumber";
    
    Given("I have {int} cucumbers in my belly", function (cucumberCount) {
        console.log("Step parsed.");
    });
    
  4. Intente ejecutar Pepino:

    $ ./node_modules/.bin/cucumber-js
    

Resultado Esperado

El módulo de definición de pasos debe cargarse y usarse para analizar el paso.

Resultado actual

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)

Clausura

Por lo que vale, realmente aprecio el trabajo que el equipo de Cucumber.js ha realizado en este proyecto, ha sido un activo importante para mí y para mi empresa. Mi equipo ha invertido algo de tiempo en construir algunos componentes de aplicaciones compartidas en módulos JS nativos, y hasta hace poco había asumido (basado en la sintaxis import en la documentación más actual) que estas cosas funcionarían con Cucumber cuando comenzó a integrarse con nuestro marco de prueba. Sin embargo, ese no parece ser el caso. Dado que el componente compartido ya está escrito e integrado en otras partes de nuestra aplicación, soy reacio a usar solo definiciones de pasos y código de soporte escrito en CommonJS debido a los desafíos de interoperabilidad.

Otras cosas que intenté hacer que esto funcionara incluyeron ...

  • Dar a los archivos de módulo nativo .mjs extensiones, según la convención de Node.js (esto hizo que la lógica de carga automática de Cucumber los pasara por alto, y el uso de --require para cargar explícitamente el módulo de definición de pasos arrojó Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
  • Envolviendo los módulos nativos en módulos CJS que usan la función dinámica import() ; el problema era que este enfoque es asincrónico, e incluso si la promesa de importación es exportada por el módulo CJS, Cucumber no parece esperar a que esa promesa se resuelva antes de continuar, porque el paso se marcó como indefinido (lo que sugiere que la definición del paso en el módulo nativo aún no estaba registrado).

Si está dentro de mis posibilidades, me complacerá enviar un PR para ayudar a resolver este problema, pero no estoy familiarizado con el funcionamiento interno de Cucumber.js y agradecería que alguien me indicara la dirección correcta.

accepted enhancement

Comentario más útil

Utilizo la extensión .js y "type":"module" ya que espero que esto se convierta en la norma a largo plazo.

Todos 13 comentarios

Me gustaría que los pepinos-js funcionen con ESM. Ya teníamos otros problemas abiertos al respecto.

Podríamos hacer una opción CLI (o usar alguna otra forma de buscar cuándo usarla) que haga que cucumber-js use import lugar de require y espere la promesa devuelta.

Dado el ejemplo que agregó, podríamos agregar un caso de prueba específicamente para esto y trabajar para que pase. Experimentar con el cambio requiere importar y esperar la promesa, luego me encontré con otro error:

⋊> ~/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)

Tomé el ejemplo (ligeramente modificado) de la documentación de definición de pasos, pero tiene razón, no funcionará como está escrito. Debido a que Cucumber.js es un módulo CommonJS, solo proporciona una exportación predeterminada. A corto plazo, probablemente solo importaría el módulo predeterminado y luego accedería a los métodos Dado / Cuándo / Entonces desde eso:

import cucumber from "cucumber";

cucumber.Given(...);

A largo plazo, sería bueno si Pepino tuviera puntos de entrada separados para la importación y la demanda, como se muestra aquí .

Gracias por mirar esto. Por lo que vale, una opción CLI para decirle a Cucumber que importe en lugar de requerir me vendría bien. También sería genial si Cucumber buscara archivos de soporte con extensiones .cjs y .mjs, y los tratara automáticamente como CommonJS y módulos nativos respectivamente.

EDITAR: solo quiero reiterar que estoy feliz de ayudar con esto si puedo; solo apúntame en la dirección correcta.

Probablemente solo importaría el módulo predeterminado y luego accedería a los métodos Dado / Cuándo / Entonces desde eso:
importar pepino de "pepino";
pepino Dado (...);

Esto no funciona. Recibo el mismo mensaje.
Tengo "tipo": "módulo" en package.json y el resto de mi proyecto está usando módulos, por lo que cambiar eso no es una opción.

Si uso alguno de estos:

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

Recibo el mismo mensaje de error que en la publicación original.
Si cambio a

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

Entonces recibo este mensaje:

En su lugar, cambie el nombre /some/path/cucumbertest/features/step_definitions.js para terminar en .cjs, cambie el código requerido para usar import () o elimine "type": "module" de project / package.json.

Si cambio el nombre de step_definitions.js a _either_ step_definitions.cjs o step_definitions.mjs , simplemente se ignora y recibo mensajes genéricos de pepino que dicen que cree stubs para mis pasos:

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

Recibo exactamente los mismos mensajes si elimino el archivo por completo, por lo que claramente se ignora.

Solución (más o menos)

Al final, la única forma en que pude hacer que el pepino se ejecute en un proyecto con "tipo": "módulo" es crear un specpackage.json separado sin "tipo": "módulo" e instalar pepino en spec/node_modules .

Hasta ahora parece estar funcionando. Veamos si sigue funcionando una vez que haya creado algunas pruebas reales.

Para los mantenedores: esta es la primera vez que uso pepino y hasta ahora me gusta. Sin embargo, esto realmente ha hecho mella en mi progreso. En lugar de pasar la tarde aprendiendo pepino, la pasé depurando importaciones. Sé lo molesto que es intentar crear una biblioteca que admita tanto las importaciones de ES6 como CJS, así que tiene mi simpatía.

Una posible solución:
Es posible publicar compilaciones de CJS y ES6 a la vez si genera un segundo archivo dist:

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

Luego agregue esta línea a package.json:

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

Esto debería permitir que el nodo resuelva las importaciones de ES6.

tiene que haber una mejor manera de hacer esto. Yo también tengo ese problema, y ​​debido a que las importaciones no se pueden usar con commonJS, tendría que usar
https://nodejs.org/api/esm.html#esm_import_expressions

En todas partes...

¿Hay alguna forma en que Pepino pueda tener una bandera que nos permita usar ES?

Una rápida para las personas interesadas en esto: cuando usa ESM en un proyecto con pepino, ¿podría:

  • Utilice la extensión .js y "type":"module"
  • Utilice la extensión .mjs
  • ¿Algo más?

¡Gracias!

Tiendo a seguir la convención de usar extensiones .mjs y .cjs para todos mis archivos JavaScript para evitar ambigüedades, y preferiría hacerlo también en este caso. Sin embargo, no sería un gran inconveniente establecer “type”: “module” a nivel de proyecto y dejarlos como archivos .js si esa es la mejor solución para más usuarios.

Utilizo la extensión .js y "type":"module" ya que espero que esto se convierta en la norma a largo plazo.

Gracias por los comentarios @adamlacoste @looeee

@davidjgoss, ¿la

¡Gracias por el codazo @ aurelien-reeves!

La versión 7.2.0 de @cucumber/cucumber ahora está en npm, incluido el soporte experimental para ESM como se describe en los documentos aquí:
https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#es-modules-experimental-nodejs-12

Si los interesados ​​pudieran probarlo e informar lo que encuentres, sería genial. Pongo un proyecto de ejemplo súper mínimo aquí:
https://github.com/davidjgoss/cucumber-esm-example

Me complace mantener este problema abierto un poco solo para recibir los primeros comentarios en un solo lugar.

Entonces, este cambio causó un problema con los marcos y formateadores de terceros que require ciertas cosas directamente (a diferencia de desde el punto de entrada). Lancé 7.2.1, que es básicamente una reversión a 7.1.0 para desbloquear a esas personas, y profundizaré en la causa y veré si podemos evitarlo sin dejar de admitir ESM. Mientras tanto, si eso no le afecta, 7.2.0 todavía está ahí para experimentar.

He estado intentando darle una oportunidad a v7.2.0 junto con la bandera --esm . utilizo testdouble en las pruebas del paquete que intento convertir a ESM.

para usar td.replaceEsm , se debe usar un cargador como --loader=testdouble . cuando intento proporcionar el cargador directamente al cli de pepino, aparece el siguiente error:

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

error: unknown option '--loader=testdouble'

como esa opción no estaba disponible, lo intenté con 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'
}

como referencia, esto fue contra el nodo v14.17.0

Sería bueno si esto funcionara con el enfoque NODE_OPTIONS , pero lo ideal es que la opción --loader sea ​​compatible directamente con la cli, como han comenzado a admitir algunos marcos de prueba. considere estos enfoques con su próxima versión que se acerque al soporte de ESM :)

¿Existe un enfoque para definir un cargador con el que esperaría trabajar con v7.2.0 ?

¿Fue útil esta página
0 / 5 - 0 calificaciones