node.jstypescriptecmascript-6playwrightcucumberjs

SyntaxError: Cannot use import statement outside a module (@cucumber/cucumber) - Node.JS, Playwright and Cucucmber


I have the following error when I try and compile my ECMAScript 6 compliant Node.js code:

$ npx cucumber-js --require features/step_definitions/steps.ts --exit
 
import { Before, Given, When, Then } from "@cucumber/cucumber";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1029:16)
    at Module._compile (internal/modules/cjs/loader.js:1078:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
    at Module.load (internal/modules/cjs/loader.js:979:32)
    at Function.Module._load (internal/modules/cjs/loader.js:819:12)
    at Module.require (internal/modules/cjs/loader.js:1003:19)
    at require (internal/modules/cjs/helpers.js:107:18)
    at /Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/api/support.js:18:32
    at Array.map (<anonymous>)
    at getSupportCodeLibrary (/Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/api/support.js:18:18)

Here's my code for analysis:

steps.ts

import { Before, Given, When, Then } from "@cucumber/cucumber";
import { Page, chromium } from "@playwright/test";
import { HomePage }  from "../../pages/HomePage";
import { SignInPage } from "../../pages/SignInPage";
import { SignInParameters } from "../../support/SignInParameters";

let homePage: HomePage;
let signInPage: SignInPage;
let signInParameters: SignInParameters;
let page: Page;

Before(async function() {
    var browser = await chromium.launch({
        headless: false,
    });
    var context = await browser.newContext();
    var page = await context.newPage();
    homePage = new HomePage(page);
    signInPage = new SignInPage(page);
    signInParameters = new SignInParameters();
});

(Step definition file. you get the gist.)

it seems as though the error is saying that cucumber-js doesn't support TypeScript type imports? But in my Npm modules, I am specifying the latest Cucumber-js version:

{
  "name": "playwright-poc",
  "version": "0.0.1",
  "description": "A Proof of Concept for Playwright",
  "scripts": {
    "test": "npx cucumber-js --require features/step_definitions/steps.ts --exit"
  },
  "type": "module",
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@playwright/test": "^1.31.2",
    "@cucumber/cucumber": "^9.0.1"
  }
}

EDIT I attempted to follow the link from @ParzhFromUkraine and got the following error by editing the file to a .mjs:

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/sam.levene/QA/playwright-automation/features/step_definitions/steps.mjs
    at new NodeError (internal/errors.js:322:7)
    at Module.load (internal/modules/cjs/loader.js:977:11)
    at Function.Module._load (internal/modules/cjs/loader.js:819:12)
    at Module.require (internal/modules/cjs/loader.js:1003:19)
    at require (internal/modules/cjs/helpers.js:107:18)
    at /Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/api/support.js:18:32
    at Array.map (<anonymous>)
    at getSupportCodeLibrary (/Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/api/support.js:18:18)
    at runCucumber (/Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/api/run_cucumber.js:34:53)
    at async Cli.run (/Users/sam.levene/QA/playwright-automation/node_modules/@cucumber/cucumber/lib/cli/index.js:50:29)

Can anyone assist with this?


Solution

  • With much effort from the devs at cucumber-js; I finally figured out what I was missing and how to fix it, so I'm now providing this here for future reference if anyone else is having the same issues.

    Firstly, I was missing cucumber.js and tsconfig.json files with the following information:

    cucumber.js
    
    export default [
        "--import features/**/*.ts",
        "--publish-quiet"
    ].join(" ");
    
    tsconfig.json
    
    {
        "compilerOptions": {
          "allowJs": true,
          "baseUrl": ".",
          "forceConsistentCasingInFileNames": true,
          "module": "esnext",
          "moduleResolution": "nodenext",
          "noImplicitAny": true,
          "noImplicitReturns": true,
          "noImplicitThis": true,
          "noUnusedLocals": true,
          "sourceMap": true,
          "strictNullChecks": true,
          "esModuleInterop": true,
          "allowSyntheticDefaultImports": true,
          "target": "es6",
          "strict": true,
          "resolveJsonModule": true,
          "isolatedModules": true,
          "noEmit": true
        },
        "exclude": [
          "node_modules",
          ".idea",
          ".vscode"
        ],
        "include": [
          "features/**/*.ts",
        ]
      }
    

    As this is using ES-Next; which is experimental, I also needed to edit the package.json such that it referenced the ESM loader and also installed ts-node:

    package.json
    
    {
      "name": "playwright-poc",
      "version": "0.0.2",
      "description": "A Proof of Concept for Playwright",
      "scripts": {
        "test": "NODE_OPTIONS=\"--loader ts-node/esm\" npx cucumber-js --parallel 10 --exit"
      },
      "type": "module",
      "keywords": [],
      "author": "Sam Levene",
      "license": "MIT",
      "devDependencies": {
        "@cucumber/cucumber": "^9.0.1",
        "@playwright/test": "^1.31.2",
        "ts-node": "^10.9.1"
      }
    }
    
    

    finally, I also had to edit my step definitions file to re-reference the imports such that it recognised the files correctly:

    steps.ts
    
    import { Before, Given, When, Then } from "@cucumber/cucumber";
    import { Page, chromium } from "@playwright/test";
    import { HomePage }  from "../../pages/HomePage.js";
    import { SignInPage } from "../../pages/SignInPage.js";
    import { ForgotPasswordPage } from "../../pages/ForgotPasswordPage.js";
    import { SignInParameters } from "../../support/SignInParameters.js";
    
    let homePage: HomePage;
    let signInPage: SignInPage;
    let signInParameters: SignInParameters;
    let forgotPasswordPage: ForgotPasswordPage;
    let page: Page;
    
    Before(async function() {
        var browser = await chromium.launch({
            headless: false,
        });
        var context = await browser.newContext();
        page = await context.newPage();
        homePage = new HomePage(page);
        signInPage = new SignInPage(page);
        forgotPasswordPage = new ForgotPasswordPage(page);
        signInParameters = new SignInParameters();
    });
    

    NB: Even though in the steps.ts it says the imports are .js files, they are still .ts files