Victor
Victor

Reputation: 941

"Error: Cannot use import statement outside a module" in Cucumber-JS step definition w/ typescript

I am getting the following error:

command: npx cucumber-js .\cucumber-e2e\
import { Given, When, Then  } from '@cucumber/cucumber';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1032:15)
    at Module._compile (node:internal/modules/cjs/loader:1067:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at C:\dev\FrontSystems.KeystonePortal\Keystone.Web\ClientApp\node_modules\@cucumber\cucumber\lib\cli\index.js:122:17
    at Array.forEach (<anonymous>)
    at Cli.getSupportCodeLibrary (C:\dev\xxxxx\xxxx.Web\ClientApp\node_modules\@cucumber\cucumber\lib\cli\index.js:120:26) 
    at Cli.run (C:\dev\xxxx\xxxx.Web\ClientApp\node_modules\@cucumber\cucumber\lib\cli\index.js:145:41)
    at async Object.run [as default] (C:\dev\xxxxx\xxxx.Web\ClientApp\node_modules\@cucumber\cucumber\lib\cli\run.js:25:18)codepath: C:\dev\xxxxx\xxxx.Web\ClientApp\cucumber-e2e\step-definitions\catalog.steps.ts

steps file:

import { Given, When, Then  } from '@cucumber/cucumber';

Given('A bank account with starting balance of {int}', (balance: number) => {
    // Write code here that turns the phrase above into concrete actions
    return 'pending';
  });

My folder structure is the following:

enter image description here

cucumber.js:

var common = [
  '--require ./cucumber-e2e/step-definitions/**/*.ts',
  '--publish-quiet',
].join(' ');

module.exports = {
  default: common,
};

tsconfig.json:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/cucumber-e2e",
    "module": "commonjs",
    "target": "es5",
    "types": [
      "jasmine",
      "jasminewd2",
      "node"
    ]
  }
}

inherited tsconfig.json:

{
  "compileOnSave": false,
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "esnext",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "resolveJsonModule": true,
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "jszip": [
        "node_modules/jszip/dist/jszip.min.js"
      ]
    },
    "plugins": [
      {
        "name": "typescript-tslint-plugin",
        "alwaysShowRuleFailuresAsWarnings": false,
        "ignoreDefinitionFiles": true,
        "configFile": "./tslint.json",
        "suppressWhileTypeErrorsPresent": false
      }
    ]
  }
}

and I've added the following packages to package.json:

"@cucumber/cucumber": "^7.3.2",
"@types/chai": "^4.3.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"protractor-cucumber-framework": "^8.4.0",
"webdriver-manager": "^12.1.8"

So, the feature files and the step definitions are being recognised, however it's throwing a syntax error when it shouldn't. I have a feeling it might be related to the package.json but I've tried multiple versions of the different packages with no positive result.

All tutorials out there seem to do it this way or very similar.

Any ideas?

Upvotes: 8

Views: 8216

Answers (2)

Oleksandr Danylchenko
Oleksandr Danylchenko

Reputation: 698

The thing that helped me was the ts-node/register and a dedicated tsconfig.json file.

I had my Cucumber configuration placed under the playwright folder, not in the root.
// playwright/cucumber.config.js
/**
 * @type {Record<string, import('@cucumber/cucumber/lib/configuration').IConfiguration>}
 */
module.exports = {
    default: {
        paths: ['playwright/features/**/*.{feature,features}'],
        require: ['playwright/steps/**/*.{step,steps}.*'],
        requireModule: ['ts-node/register'], // Transpiles the `.ts` steps
        formatOptions: { snippetInterface: 'async-await' },
    }
};
// playwright/tsconfig.json
{
    "compilerOptions": {
        "skipLibCheck": true,
        "noErrorTruncation": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "resolveJsonModule": true,
        "lib": ["esnext", "dom"],
        "moduleResolution": "node",
        "target": "esnext",
        "module": "commonjs"
    },
    "include": ["playwright/**/*.ts"]
}
And the most important thing I missed at first - the TS_NODE_PROJECT='playwright/tsconfig.json' option passed to the cucumber-js executable:
package.json
{
  ...
  "scripts": {
    ...
    "test-playwright-cucumber": "TS_NODE_PROJECT='playwright/tsconfig.json' cucumber-js --config playwright/cucumber.config.js"
}

Upvotes: 1

Ben
Ben

Reputation: 1371

If you haven't specified the type of module in your package.json, it will default to CommonJS. In this context, you cannot use the import syntax, you have to rely on require.

There are 2 ways to resolve this:

  1. Change your import syntax to use require:
const { Given, When, Then } = require('@cucumber/cucumber');
  1. Change your module type to an ES module:
// package.json
{
  ...
  "type": "module",
  ...
}

Note that in this second case, if the module you are requesting is a CommonJS module, it may not support named exports and you will have to fallback to the following syntax:

import Cucumber from '@cucumber/cucumber';
const { Given, When, Then } = Cucumber;

Upvotes: 3

Related Questions