Cucumber-js: Native JS-Modulunterstützung in Schrittdefinitionen und Supportcode

Erstellt am 3. Apr. 2020  ·  13Kommentare  ·  Quelle: cucumber/cucumber-js

Problem

Wenn ich versuche, Cucumber.js mit einer Schrittdefinition auszuführen, die in einem nativen ECMAScript-Modul definiert ist (wie hier dokumentiert), schlägt der Versuch mit einer Warnung fehl, dass ich require() von CommonJS in einem ES-Modul verwende.

Gurken.js- Version: 6.0.5
Knotenversion : 13.8.0

Schritte zum Reproduzieren

  1. Richten Sie ein grundlegendes NPM-Paketverzeichnis mit npm init und npm i cucumber

    • Legen Sie "type": "module" in der Datei package.json , um sicherzustellen, dass JS-Dateien als native Module behandelt werden

  2. Erstellen Sie eine grundlegende Feature-Datei, features/mjs.feature :

    Feature: Native JS Modules
    
        Scenario: Load a native JS module step definition
            Given I have 42 cucumbers in my belly
    
  3. Erstellen Sie eine grundlegende Schrittdefinitionsdatei, features/step_definitions.js :

    import { Given } from "cucumber";
    
    Given("I have {int} cucumbers in my belly", function (cucumberCount) {
        console.log("Step parsed.");
    });
    
  4. Versuch, Gurke auszuführen:

    $ ./node_modules/.bin/cucumber-js
    

Erwartetes Ergebnis

Das Schrittdefinitionsmodul sollte geladen und zum Analysieren des Schritts verwendet werden.

Tatsächliche Ergebnis

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)

Schließen

Für das, was es wert ist, schätze ich die Arbeit des Cucumber.js-Teams in diesem Projekt sehr, es war ein großer Gewinn für mich und mein Unternehmen. Mein Team hat einige Zeit in den Aufbau einiger gemeinsam genutzter Anwendungskomponenten in nativen JS-Modulen investiert, und bis vor kurzem war ich davon ausgegangen (basierend auf der import Syntax in der aktuellsten Dokumentation), dass dieses Zeug mit Cucumber funktionieren würde, wenn wir dazu gekommen, sich in unser Test-Framework zu integrieren. Das scheint jedoch nicht der Fall zu sein. Da die gemeinsam genutzte Komponente bereits geschrieben und in andere Teile unserer Anwendung integriert ist, verwende ich aufgrund von Interoperabilitätsproblemen nur ungern nur Schrittdefinitionen und unterstützten Code, der in CommonJS geschrieben wurde.

Andere Dinge, die ich versucht habe, um dies zum Laufen zu bringen, sind...

  • Den nativen Moduldateien .mjs Erweiterungen gemäß der Node.js-Konvention zu geben (dies führte dazu, dass sie von der automatischen Ladelogik von Cucumber übersehen wurden, und die Verwendung von --require zum expliziten Laden des Schrittdefinitionsmoduls führte zu Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
  • Einpacken der nativen Module in CJS-Module, die die dynamische import() Funktion verwenden; Das Problem bestand darin, dass dieser Ansatz asynchron ist, und selbst wenn das Importversprechen vom CJS-Modul exportiert wird, scheint Cucumber nicht zu warten, bis dieses Versprechen aufgelöst wird, bevor es fortfährt, da der Schritt als nicht definiert markiert wurde (was darauf hindeutet, dass die Schrittdefinition im nativen Modul war noch nicht registriert).

Wenn es in meinen Möglichkeiten liegt, würde ich gerne eine PR einreichen, um dieses Problem zu lösen, aber ich bin mit der internen Funktionsweise von Cucumber.js nicht vertraut und würde mich freuen, wenn mir jemand den richtigen Weg weisen könnte.

accepted enhancement

Hilfreichster Kommentar

Ich verwende die Erweiterung .js und "type":"module" da ich erwarte, dass dies auf lange Sicht zur Norm wird.

Alle 13 Kommentare

Ich möchte gurken-js mit ESM arbeiten lassen. Wir hatten bereits einige andere Fragen dazu.

Wir könnten eine CLI-Option erstellen (oder eine andere Methode verwenden, um zu suchen, wann sie verwendet werden soll), die gurken-js dazu bringt, import anstatt zu verlangen und auf das zurückgegebene Versprechen zu warten.

Angesichts des von Ihnen hinzugefügten Beispiels könnten wir speziell dafür einen Testfall hinzufügen und daran arbeiten, ihn zu bestehen. Beim Experimentieren mit dem Wechsel erfordern den Import und das Warten auf das Versprechen, dann stieß ich auf einen anderen Fehler:

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

Ich habe das Beispiel (leicht modifiziert) aus der Schrittdefinitionsdokumentation genommen, aber Sie haben Recht, es funktioniert nicht wie geschrieben. Da Cucumber.js ein CommonJS-Modul ist, bietet es nur einen Standardexport. Kurzfristig würde ich wahrscheinlich einfach den Modulstandard importieren und dann auf die Methoden von Given/When/Then zugreifen:

import cucumber from "cucumber";

cucumber.Given(...);

Längerfristig wäre es schön, wenn Gurke separate Einstiegspunkte für den Import und Bedarf hätte, wie hier gezeigt.

Danke, dass du dir das angesehen hast. Für was es wert ist, würde mir eine CLI-Option, die Cucumber anweist, zu importieren, anstatt es zu verlangen, gut passen. Es wäre auch großartig, wenn Cucumber nach Unterstützungsdateien mit den Erweiterungen .cjs und .mjs suchen und diese automatisch als CommonJS- bzw. native Module behandeln würde.

EDIT: Ich möchte nur wiederholen, dass ich gerne bei all dem helfe, wenn ich kann; zeig mir einfach die richtige richtung.

Ich würde wahrscheinlich einfach den Modulstandard importieren und dann auf die Methoden von Given/When/Then zugreifen:
Gurke aus "Gurke" importieren;
Gurke.Gegeben(...);

Dies funktioniert nicht. Ich bekomme die gleiche Meldung.
Ich habe "type": "module" in package.json und der Rest meines Projekts verwendet Module, daher ist eine Änderung keine Option.

Wenn ich eines davon verwende:

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

Ich erhalte die gleiche Fehlermeldung wie im ursprünglichen Beitrag.
Wenn ich ändere zu

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

Dann bekomme ich diese Meldung:

Benennen Sie stattdessen /some/path/cucumbertest/features/step_definitions.js so um, dass es auf .cjs endet, ändern Sie den erforderlichen Code, um import() zu verwenden, oder entfernen Sie "type": "module" aus project/package.json.

Wenn ich step_definitions.js in _entweder_ step_definitions.cjs oder step_definitions.mjs umbenenne, wird es einfach ignoriert und ich erhalte allgemeine Nachrichten von Gurke, die sagen, dass ich Stubs für meine Schritte erstellen soll:

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

Ich erhalte genau die gleichen Meldungen, wenn ich die Datei vollständig lösche, also wird sie eindeutig ignoriert.

Lösung (irgendwie)

Letztendlich konnte ich gurke in einem Projekt mit "type": "module" nur ausführen, indem ich eine separate specpackage.json ohne "type": "module" erstellt und gurke in spec/node_modules installiert habe.

Bisher scheint es zu funktionieren. Mal sehen, ob es noch funktioniert, nachdem ich einige tatsächliche Tests erstellt habe.

An die Betreuer: Dies ist das erste Mal, dass ich Gurke verwende, und ich mag sie bis jetzt. Dies hat jedoch meine Fortschritte wirklich beeinträchtigt. Anstatt den Nachmittag damit zu verbringen, Gurken zu lernen, verbrachte ich ihn damit, Importe zu debuggen. Ich weiß, wie nervig es ist, eine Bibliothek zu erstellen, die sowohl ES6-Importe als auch CJS unterstützt, also haben Sie mein Mitgefühl.

Eine mögliche Lösung:
Es ist möglich, sowohl CJS- als auch ES6-Builds gleichzeitig zu veröffentlichen, wenn Sie eine zweite dist-Datei generieren:

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

Fügen Sie dann diese Zeile zu package.json hinzu:

"module": "dist/cucumber.module.js"

Dies sollte es dem Knoten ermöglichen, ES6-Importe aufzulösen.

es muss einen besseren Weg geben, dies zu tun. Ich habe auch dieses Problem, und da Importe nicht mit commonJS verwendet werden können, müsste ich es verwenden
https://nodejs.org/api/esm.html#esm_import_expressions

überall, überallhin, allerorts...

Gibt es eine Möglichkeit, dass Gurke ein Flag haben kann, um es uns zu ermöglichen, ES zu verwenden?

Kurz für Leute, die sich dafür interessieren: Wenn Sie ESM in einem Projekt mit Gurke verwenden, würden Sie:

  • Verwenden Sie die Erweiterung .js und "type":"module"
  • Verwenden Sie die Erweiterung .mjs
  • Etwas anderes?

Vielen Dank!

Ich neige dazu, der Konvention zu folgen, die Erweiterungen .mjs und .cjs für alle meine JavaScript-Dateien zu verwenden, um Mehrdeutigkeiten zu vermeiden, und würde dies auch in diesem Fall vorziehen. Es wäre jedoch keine große Unannehmlichkeit, "type": "module" auf Projektebene festzulegen und diese als .js-Dateien zu belassen, wenn dies für mehr Benutzer die bessere Lösung ist.

Ich verwende die Erweiterung .js und "type":"module" da ich erwarte, dass dies auf lange Sicht zur Norm wird.

Danke für das Feedback @adamlacoste @looeee

@davidjgoss reicht das Zusammenführen von #1589 aus, um dieses Problem zu schließen? Oder brauchen wir etwas anderes?

Danke für den Schubs @aurelien-reeves!

Version 7.2.0 von @cucumber/cucumber ist jetzt auf npm, einschließlich experimenteller Unterstützung für ESM, wie in den Dokumenten hier beschrieben:
https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#es-modules-experimental-nodejs-12

Wenn Interessierte es ausprobieren und berichten könnten, was ihr findet, wäre das toll. Ich habe hier ein super minimales Beispielprojekt hochgeladen:
https://github.com/davidjgoss/cucumber-esm-example

Ich freue mich, dieses Thema noch eine Weile offen zu halten, nur um das frühe Feedback an einem Ort zu erfassen.

Diese Änderung verursachte also ein Problem mit Frameworks und Formatierern von Drittanbietern, die bestimmte Dinge direkt require (im Gegensatz zum Einstiegspunkt) verursachen. Ich habe 7.2.1 veröffentlicht, was im Grunde eine Rückkehr zu 7.1.0 ist, um diese Leute zu entsperren, und ich werde der Ursache nachgehen und sehen, ob wir es vermeiden können, während wir weiterhin ESM unterstützen. In der Zwischenzeit, wenn Sie davon nicht betroffen sind, können Sie mit 7.2.0 noch experimentieren.

Ich habe versucht, v7.2.0 zusammen mit dem --esm Flag eine Chance zu geben. Ich verwende testdouble in den Tests für das Paket, das ich in ESM konvertieren möchte.

Um td.replaceEsm , muss ein Loader wie --loader=testdouble . Wenn ich versuche, den Loader direkt dem Gurken-Cli bereitzustellen, erhalte ich die folgende Fehlermeldung:

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

error: unknown option '--loader=testdouble'

Da diese Option nicht verfügbar war, habe ich es dann mit NODE_OPTIONS versucht:

> 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'
}

als Referenz, dies war gegen den Knoten v14.17.0

Es wäre schön, wenn dies mit dem NODE_OPTIONS Ansatz funktionieren würde, aber im Idealfall würde die Option --loader direkt von der cli unterstützt, wie es einige Testframeworks inzwischen unterstützen. Bitte berücksichtigen Sie diese Ansätze bei Ihrer nächsten Version, die sich der ESM-Unterstützung nähert :)

Gibt es einen Ansatz zum Definieren eines Loaders, von dem Sie erwarten würden, dass er mit v7.2.0 ?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen