Cucumber-js: Встроенная поддержка модуля JS в определениях шагов и коде поддержки

Созданный на 3 апр. 2020  ·  13Комментарии  ·  Источник: cucumber/cucumber-js

Проблема

Когда я пытаюсь запустить Cucumber.js с определением шага, определенным в собственном модуле ECMAScript (как описано здесь ), попытка завершается ошибкой с предупреждением о том, что я использую CommonJS require() в модуле ES.

Версия Cucumber.js: 6.0.5
Версия узла: 13.8.0

Действия по воспроизведению

  1. Установите базовый каталог пакетов NPM, используя npm init и npm i cucumber

    • Установите "type": "module" в файле package.json чтобы файлы JS обрабатывались как собственные модули.

  2. Создайте базовый файл функций features/mjs.feature :

    Feature: Native JS Modules
    
        Scenario: Load a native JS module step definition
            Given I have 42 cucumbers in my belly
    
  3. Создайте базовый файл определения шага features/step_definitions.js :

    import { Given } from "cucumber";
    
    Given("I have {int} cucumbers in my belly", function (cucumberCount) {
        console.log("Step parsed.");
    });
    
  4. Попытка запустить Cucumber:

    $ ./node_modules/.bin/cucumber-js
    

Ожидаемый результат

Модуль определения шага должен быть загружен и использоваться для анализа шага.

Фактический результат

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)

Закрытие

Как бы то ни было, я очень ценю работу, которую команда Cucumber.js проделала над этим проектом, это был большой актив для меня и моей компании. Моя команда потратила некоторое время на создание некоторых общих компонентов приложения в собственных модулях JS, и до недавнего времени я предполагал (на основе синтаксиса import в самой последней документации), что этот материал будет работать с Cucumber, когда мы добрался до интеграции с нашей тестовой средой. Однако, похоже, это не так. Поскольку общий компонент уже написан и интегрирован в другие части нашего приложения, я не хочу использовать только определения шагов и код поддержки, написанный на CommonJS, из-за проблем с совместимостью.

Другие вещи, которые я пытался заставить это работать, включены ...

  • Предоставление файлам собственных модулей расширений .mjs соответствии с соглашением Node.js (это привело к тому, что логика автоматической загрузки Cucumber не обратила на них внимания, а использование --require для явной загрузки модуля определения шага привело к Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
  • Оборачивание собственных модулей в модули CJS, которые используют динамическую функцию import() ; проблема заключалась в том, что этот подход является асинхронным, и даже если обещание импорта экспортируется модулем CJS, Cucumber, похоже, не ожидает разрешения этого обещания, прежде чем продолжить, потому что шаг был помечен как неопределенный (предполагая, что определение шага в родном модуле еще не прописался).

Если это в моих силах, я был бы рад отправить PR, чтобы помочь решить эту проблему, но я не знаком с внутренней работой Cucumber.js и был бы признателен, если бы кто-нибудь мог указать мне в правильном направлении.

accepted enhancement

Самый полезный комментарий

Я использую расширение .js и "type":"module" так как ожидаю, что это станет нормой в долгосрочной перспективе.

Все 13 Комментарий

Я бы хотел, чтобы cucumber-js работал с ESM. У нас уже были открыты некоторые другие вопросы по этому поводу.

Мы могли бы создать параметр CLI (или использовать другой способ поиска, когда его использовать), который заставляет cucumber-js использовать import вместо require и ждать возвращенного обещания.

Учитывая добавленный вами пример, мы могли бы добавить тестовый пример специально для этого и работать над тем, чтобы он прошел. Экспериментируя с переключением, требующим импорта и ожидания обещания, я столкнулся с другой ошибкой:

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

Я взял пример (слегка измененный) из документации по определению шага, но вы правы, он не будет работать так, как написано. Поскольку Cucumber.js является модулем CommonJS, он предоставляет только экспорт по умолчанию. В краткосрочной перспективе я, вероятно, просто импортирую модуль по умолчанию, а затем получу доступ к методам Given / When / Then из этого:

import cucumber from "cucumber";

cucumber.Given(...);

В долгосрочной перспективе было бы неплохо, если бы у Cucumber были отдельные точки входа для импорта и требования, как показано здесь .

Спасибо, что посмотрели на это. Как бы то ни было, вариант интерфейса командной строки, указывающий Cucumber на импорт, а не на требование, меня вполне устроил. Также было бы замечательно, если бы Cucumber искал файлы поддержки с расширениями .cjs и .mjs и автоматически рассматривал их как CommonJS и собственные модули соответственно.

РЕДАКТИРОВАТЬ: просто хочу повторить, что я рад помочь с любым из этого, если могу; просто укажите мне правильное направление.

Я бы, вероятно, просто импортировал модуль по умолчанию, а затем получил бы доступ к методам Given / When / Then из этого:
импортный огурец из "огурца";
огурец.Данный (...);

Это не работает. Я получаю то же сообщение.
У меня есть "type": "module" в package.json, а остальная часть моего проекта использует модули, поэтому изменение этого варианта недопустимо.

Если я использую одно из них:

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

Я получаю то же сообщение об ошибке, что и исходное сообщение.
Если я перейду на

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

Затем я получаю это сообщение:

Вместо этого переименуйте /some/path/cucumbertest/features/step_definitions.js так, чтобы он заканчивался на .cjs, измените требуемый код для использования import () или удалите "type": "module" из project / package.json.

Если я переименую step_definitions.js в _either_ step_definitions.cjs или step_definitions.mjs , он просто игнорируется, и я получаю общие сообщения от огурца, в которых говорится о создании заглушек для моих шагов:

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

Я получаю точно такие же сообщения, если полностью удаляю файл, поэтому он явно игнорируется.

Решение (вроде)

В конце концов, единственный способ заставить огурец запускаться в проекте с "type": "module" - это создать отдельный specpackage.json без "type": "module" и установить огурец в spec/node_modules .

Пока вроде работает. Посмотрим, работает ли он по-прежнему, когда я создам несколько реальных тестов.

Сопровождающим: я впервые использую огурец, и пока мне это нравится. Однако это действительно помешало моему прогрессу. Вместо того, чтобы тратить полдня на изучение огурца, я потратил его на отладку импорта. Я знаю, как досадно пытаться создать библиотеку, поддерживающую как импорт ES6, так и CJS, так что я вам сочувствую.

Возможное решение:
Можно опубликовать сборки CJS и ES6 одновременно, если вы создадите второй файл dist:

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

Затем добавьте эту строку в package.json:

"модуль": "dist / cucumber.module.js"

Это должно позволить узлу разрешить импорт ES6.

должен быть лучший способ сделать это. У меня тоже есть эта проблема, и поскольку импорт не может использоваться с commonJS, мне пришлось бы использовать
https://nodejs.org/api/esm.html#esm_import_expressions

где угодно...

есть ли способ, которым Cucumber может иметь флаг, который помогает нам использовать ES?

Быстрый для людей, заинтересованных в этом: когда вы используете ESM в проекте с огурцом, вы бы:

  • Используйте расширение .js и "type":"module"
  • Используйте расширение .mjs
  • Что-то другое?

Спасибо!

Я стараюсь следовать соглашению об использовании расширений .mjs и .cjs для всех моих файлов JavaScript, чтобы избежать двусмысленности, и я бы предпочел сделать это и в этом случае. Однако не будет большим неудобством установить «type»: «module» на уровне проекта и оставить их как файлы .js, если это лучшее решение для большего числа пользователей.

Я использую расширение .js и "type":"module" так как ожидаю, что это станет нормой в долгосрочной перспективе.

Спасибо за отзыв @adamlacoste @looeee

@davidjgoss достаточно ли

Спасибо за подталкивающий @ aurelien-reeves!

Версия 7.2.0 @cucumber/cucumber теперь находится на npm, включая экспериментальную поддержку ESM, как описано в документации здесь:
https://github.com/cucumber/cucumber-js/blob/master/docs/cli.md#es-modules-experimental-nodejs-12

Если бы заинтересованные могли попробовать и сообщить, что вы нашли, это было бы здорово. Я разместил здесь суперминимальный пример проекта:
https://github.com/davidjgoss/cucumber-esm-example

Мы рады оставить этот вопрос открытым ненадолго, просто чтобы собрать ранние отзывы в одном месте.

Таким образом, это изменение вызвало проблему со сторонними фреймворками и программами форматирования, которые напрямую (а не из точки входа) использовали require определенные вещи. Я выпустил 7.2.1, который в основном представляет собой возврат к 7.1.0, чтобы разблокировать этих людей, и я раскрою причину и посмотрю, сможем ли мы ее избежать, продолжая поддерживать ESM. А пока, если это вас не коснется, можно поэкспериментировать с 7.2.0.

я пытался дать v7.2.0 шанс вместе с флагом --esm . Я использую testdouble в тестах для пакета, который я пытаюсь преобразовать в ESM.

чтобы использовать td.replaceEsm , необходимо использовать загрузчик, например --loader=testdouble . когда я пытаюсь предоставить загрузчик непосредственно в cli огурца, я получаю следующую ошибку:

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

error: unknown option '--loader=testdouble'

поскольку эта опция была недоступна, я попытался использовать 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'
}

для справки, это было против узла v14.17.0

Было бы неплохо, если бы это работало с подходом NODE_OPTIONS , но в идеале опция --loader будет поддерживаться непосредственно cli, как некоторые тестовые фреймворки начали поддерживать. пожалуйста, рассмотрите эти подходы со своей следующей версией, которая приближается к поддержке ESM :)

есть ли подход к определению загрузчика, который, как вы ожидаете, будет работать с v7.2.0 ?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги