Cucumber-js: Prise en charge du module JS natif dans les définitions d'étape et le code de prise en charge

Créé le 3 avr. 2020  ·  13Commentaires  ·  Source: cucumber/cucumber-js

Problème

Lorsque j'essaie d'exécuter Cucumber.js avec une définition d'étape définie dans un module ECMAScript natif (comme documenté ici ), la tentative échoue avec un avertissement indiquant que j'utilise require() CommonJS sur un module ES.

Version Cucumber.js : 6.0.5
Version du nœud : 13.8.0

Étapes à reproduire

  1. Configurez un répertoire de package NPM de base en utilisant npm init et npm i cucumber

    • Définissez "type": "module" dans le fichier package.json pour vous assurer que les fichiers JS seront traités comme des modules natifs

  2. Créez un fichier de fonctionnalité de base, features/mjs.feature :

    Feature: Native JS Modules
    
        Scenario: Load a native JS module step definition
            Given I have 42 cucumbers in my belly
    
  3. Créez un fichier de définition d'étape de base, features/step_definitions.js :

    import { Given } from "cucumber";
    
    Given("I have {int} cucumbers in my belly", function (cucumberCount) {
        console.log("Step parsed.");
    });
    
  4. Essayez d'exécuter Concombre :

    $ ./node_modules/.bin/cucumber-js
    

Résultat attendu

Le module de définition d'étape doit être chargé et utilisé pour analyser l'étape.

Résultat actuel

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)

Fermeture

Pour ce que ça vaut, j'apprécie vraiment le travail que l'équipe Cucumber.js a mis sur ce projet, cela a été un atout majeur pour moi et mon entreprise. Mon équipe a investi du temps dans la création de composants d'application partagés dans des modules JS natifs, et jusqu'à récemment, j'avais supposé (sur la base de la syntaxe import dans la documentation la plus récente) que cela fonctionnerait avec Cucumber lorsque nous s'est mis à s'intégrer à notre framework de test. Cela ne semble pas être le cas, cependant. Étant donné que le composant partagé est déjà écrit et intégré dans d'autres parties de notre application, je suis réticent à n'utiliser que des définitions d'étape et du code de support écrit en CommonJS en raison de problèmes d'interopérabilité.

D'autres choses que j'ai essayé de faire fonctionner inclus...

  • Donner aux fichiers du module natif des extensions .mjs , selon la convention Node.js (cela les a fait ignorer par la logique de chargement automatique de Cucumber, et en utilisant --require pour charger explicitement le module de définition d'étape a jeté Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
  • Envelopper les modules natifs dans des modules CJS qui utilisent la fonction dynamique import() ; le problème était que cette approche est asynchrone, et même si la promesse d'importation est exportée par le module CJS, Cucumber ne semble pas attendre que cette promesse soit résolue avant de continuer, car l'étape a été marquée comme non définie (ce qui suggère que la définition de l'étape dans le module natif n'était pas encore enregistré).

Si cela est dans ma capacité, je serais heureux de soumettre un PR pour aider à résoudre ce problème, mais je ne connais pas le fonctionnement interne de Cucumber.js et j'apprécierais que quelqu'un puisse m'orienter dans la bonne direction.

accepted enhancement

Commentaire le plus utile

J'utilise l'extension .js et "type":"module" car je pense que cela deviendra la norme à long terme.

Tous les 13 commentaires

J'aimerais que cucumber-js travaille avec ESM. Nous avions déjà ouvert d'autres problèmes à ce sujet.

Nous pourrions créer une option CLI (ou utiliser un autre moyen pour rechercher quand l'utiliser) qui fait que cucumber-js utilise import au lieu de require et attend la promesse renvoyée.

Compte tenu de l'exemple que vous avez ajouté, nous pourrions ajouter un cas de test spécifiquement pour cela et travailler à sa réussite. Expérimenter avec la commutation nécessite d'importer et d'attendre la promesse, j'ai ensuite rencontré une autre erreur:

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

J'ai pris l'exemple (légèrement modifié) de la documentation de définition d'étape, mais vous avez raison, cela ne fonctionnera pas tel qu'il est écrit. Étant donné que Cucumber.js est un module CommonJS, il ne fournit qu'une exportation par défaut. À court terme, je voudrais probablement simplement importer la valeur par défaut du module, puis accéder aux méthodes Given/When/Then à partir de cela :

import cucumber from "cucumber";

cucumber.Given(...);

À plus long terme, ce serait bien si le concombre avait des points d'entrée séparés pour l'importation et l'exigence, comme indiqué ici .

Merci d'avoir regarder ceci. Pour ce que ça vaut, une option CLI pour dire à Cucumber d'importer au lieu d'exiger me conviendrait parfaitement. Ce serait également formidable si Cucumber recherchait des fichiers de support avec les extensions .cjs et .mjs, et les traitait automatiquement comme des modules CommonJS et natifs respectivement.

EDIT : je veux juste réitérer que je suis heureux d'aider avec tout cela si je le peux ; orientez-moi simplement dans la bonne direction.

Je voudrais probablement juste importer la valeur par défaut du module, puis accéder aux méthodes Given/When/Then à partir de cela :
importer du concombre de "concombre";
concombre.Étant donné(...);

Cela ne fonctionne pas. Je reçois le même message.
J'ai "type": "module" dans package.json et le reste de mon projet utilise des modules, donc changer ce n'est pas une option.

Si j'utilise l'un de ces éléments :

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

J'obtiens le même message d'erreur que le message d'origine.
Si je change pour

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

Puis j'obtiens ce message :

Au lieu de cela, renommez /some/path/cucumbertest/features/step_definitions.js pour qu'il se termine par .cjs, modifiez le code requis pour utiliser import() ou supprimez "type": "module" de project/package.json.

Si je renomme step_definitions.js en _soit_ step_definitions.cjs ou step_definitions.mjs , il est simplement ignoré et je reçois des messages génériques de concombre disant de créer des talons pour mes étapes :

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

Je reçois exactement les mêmes messages si je supprime complètement le fichier, il est donc clairement ignoré.

Solution (en quelque sorte)

En fin de compte, la seule façon dont j'ai pu faire fonctionner le concombre dans un projet avec "type": "module" est de créer un specpackage.json séparé sans "type": "module" et d'installer le concombre dans spec/node_modules .

Jusqu'à présent, cela semble fonctionner. Voyons si cela fonctionne toujours une fois que j'ai créé de vrais tests.

Aux responsables : c'est la première fois que j'utilise du concombre, et je l'aime jusqu'à présent. Cependant, cela a vraiment mis un frein à mes progrès. Au lieu de passer l'après-midi à apprendre le concombre, je l'ai passé à déboguer les importations. Je sais à quel point il est ennuyeux d'essayer de créer une bibliothèque prenant en charge les importations ES6 et CJS, vous avez donc ma sympathie.

Une solution envisageable :
Il est possible de publier à la fois les builds CJS et ES6 si vous générez un deuxième fichier dist :

dist/concombre.js
dist/concombre.module.js

Ajoutez ensuite cette ligne à package.json :

"module": "dist/concombre.module.js"

Cela devrait permettre au nœud de résoudre les importations ES6.

il doit y avoir une meilleure façon de le faire. J'ai aussi ce problème, et parce que les importations ne peuvent pas être utilisées avec commonJS, je devrais utiliser
https://nodejs.org/api/esm.html#esm_import_expressions

partout...

Existe-t-il un moyen pour Cucumber d'avoir un indicateur pour nous permettre d'utiliser ES ?

Un rapide pour les personnes intéressées par cela : lorsque vous utilisez ESM dans un projet avec du concombre, voudriez-vous :

  • Utilisez l'extension .js et "type":"module"
  • Utilisez l'extension .mjs
  • Autre chose?

Merci!

J'ai tendance à suivre la convention d'utiliser les extensions .mjs et .cjs pour tous mes fichiers JavaScript afin d'éviter toute ambiguïté, et ce serait ma préférence de le faire dans ce cas également. Cependant, ce ne serait pas un inconvénient majeur de définir "type": "module" au niveau du projet et de les laisser sous forme de fichiers .js si c'est la meilleure solution pour plus d'utilisateurs.

J'utilise l'extension .js et "type":"module" car je pense que cela deviendra la norme à long terme.

Merci pour le retour @adamlacoste @looeee

@davidjgoss Est-ce que la

Merci pour le coup de pouce @aurelien-reeves !

La version 7.2.0 de @cucumber/cucumber est désormais disponible sur npm, y compris la prise en charge expérimentale d'ESM comme décrit dans la documentation ici :
https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#es-modules-experimental-nodejs-12

Si les personnes intéressées pouvaient essayer et rapporter ce que vous trouvez, ce serait formidable. J'ai mis un exemple de projet super minimal ici:
https://github.com/davidjgoss/cucumber-esm-example

Heureux de garder ce problème ouvert un peu juste pour recueillir les premiers commentaires en un seul endroit.

Ce changement a donc causé un problème avec les frameworks et les formateurs tiers qui require certains éléments directement (par opposition au point d'entrée). J'ai publié la version 7.2.1 qui est essentiellement un retour à la version 7.1.0 pour débloquer ces personnes, et je vais creuser la cause et voir si nous pouvons l'éviter tout en soutenant ESM. En attendant, si cela ne vous concerne pas, la 7.2.0 est toujours là pour expérimenter.

J'ai essayé de donner un coup à v7.2.0 avec le drapeau --esm . J'utilise testdouble dans les tests du package que j'essaie de convertir en ESM.

pour utiliser td.replaceEsm , un chargeur doit être utilisé comme --loader=testdouble . lorsque j'essaie de fournir le chargeur directement au cli de concombre, j'obtiens l'erreur suivante :

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

error: unknown option '--loader=testdouble'

comme cette option n'était pas disponible, j'ai alors essayé avec 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'
}

pour référence, c'était contre le nœud v14.17.0

ce serait bien si cela fonctionnait avec l'approche NODE_OPTIONS , mais idéalement, l'option --loader serait prise en charge directement par la cli comme certains frameworks de test ont commencé à le prendre en charge. veuillez considérer ces approches avec votre prochaine version qui approche le support ESM :)

existe-t-il une approche pour définir un chargeur avec lequel vous vous attendriez à travailler avec v7.2.0 ?

Cette page vous a été utile?
0 / 5 - 0 notes