Search code examples
vuejs3playwrightcucumberjs

CucumberJS with Vue 3 and PlayWright: TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".vue"


I can use the PlayWright experimental component testing to test my HelloWorld.vue component. This works:

import { test, expect } from '@playwright/experimental-ct-vue';
import HelloWorld from './HelloWorld.vue';

test.use({ viewport: { width: 500, height: 500 } });

test('should work', async ({ mount }) => {
  const component = await mount(HelloWorld, {
    props: {
      msg: 'You did it!'
    }
  });

  await expect(component).toContainText('Vite + Vue');
});

But now I want to add CucumberJS into the stack. Since the Vue app uses ESM modules my step definition file is an mjs file so I can use "import" rather than "require".

The issue is I get the TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".vue" where I am importing the component.

cmp-helloworld.mjs:

import { Given, When, Then } from '@cucumber/cucumber';
import { test, expect } from '@playwright/experimental-ct-vue';

import HelloWorld from '../../components/HelloWorld.vue'; // <== Throws the error

let component;

When('I mount the component with the message {string}', async function (message) {
  component = await mount(HelloWorld, {
    props: {
      msg: message
    }
  });
});

// Then ...

Any ideas about how can I do this, please? It will be a bummer if I can't use BDD with PlayWright component tests.

And... if I solve the import issue, yes I will need to get the mount function from somewhere. But that is the next hurdle. Has anyone achieved this combination?

Edit: sharing package.json

{
  "name": "myappct",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test:unit": "vitest",
    "test:e2e": "playwright test -c playwright-e2e.conf.js",
    "test-ct": "playwright test -c playwright-ct.config.js",
    "pretest": "node ./src/test/helpers/init.mjs",
    "test": "cross-env TEST_PROFILE=test cucumber-js --config src/test/config/cucumber.mjs || true",
    "posttest": "node cucumber-html-report.js",
    "test:failed": "cucumber-js -p rerun @rerun.txt"
  },
  "dependencies": {
    "@dotenvx/dotenvx": "^1.5.0",
    "vue": "^3.4.29"
  },
  "devDependencies": {
    "@cucumber/cucumber": "^10.8.0",
    "@playwright/experimental-ct-vue": "^1.45.1",
    "@playwright/test": "^1.45.1",
    "@types/node": "^20.14.10",
    "@vitejs/plugin-vue": "^5.0.5",
    "@vue/test-utils": "^2.4.6",
    "cross-env": "^7.0.3",
    "fs-extra": "^11.2.0",
    "jsdom": "^24.1.0",
    "multiple-cucumber-html-reporter": "^3.6.2",
    "vite": "^5.3.1",
    "vite-plugin-vue-devtools": "^7.3.1",
    "vitest": "^1.6.0"
  }
}

And just for completeness, here is a working example of a PlayWright + Cucumber (JS) e2e test. I was hoping to be able to achieve something similar with component testing.

import { Given, When, Then, setDefaultTimeout } from '@cucumber/cucumber';
import { test, expect, chromium } from '@playwright/test';

// Global browser and page state for scenarios
let browser;
let page;

Given('I am on the Playwright website', async function () {
  browser = await chromium.launch({ headless: true });
  page = await browser.newPage();
  await page.goto('https://playwright.dev/');
});

Then('the page title should be {string}', async function (title) {
  // Turn the string into a regular expression
  const re = new RegExp(title);
  await expect(page).toHaveTitle(re);
  await browser.close();
});

When('I click on the {string} link', async function (linkText) {
  // Click the get started link.
  await page.getByRole('link', { name: linkText }).click();
});

Then('I should be redirected to the {string} page', async function (pageName) {
  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: pageName })).toBeVisible();
  await browser.close();
});

Solution

  • After much testing and hair-pulling I find that for now it is not possible to use CucumberJS to test Vue components. PlayWright + CucumberJS is fine for Vue e2e tests but not for component testing.

    One reason is a fundamental conflict in the module systems. While CucumberJS now does support ES modules, Vue is using Vite to transpile (whether you use CommonJS or add "type":"module" to package.json to get ESM). So, when the Cucumber step file encounters an import like:

    import HelloWorld from '../../components/HelloWorld.vue';
    

    it expects an ESM import but it is not such a thing. Vue can handle this "importing" via Vite, but Cucumber cannot so it wont know what to do with the file even if it did manage to import it.

    I am using Vitest and Vitest-cucumber for Vue / Quasar component testing and that works well enough. Vitest-cucumber is not a CucumberJS implementation - it is a partial implementation of Gherkin with wrapper functions around "describe / it" via Given, When, Then functions etc. But at least I can write Gherkin feature files for better documentation than just the unit tests.

    If you want to use that for Quasar, see also: here

    :-)