概要
前回 は Cucumberのみを使ってドメインの単体テストを行った。
今回は Playwright を使ってe2eテストを行った。bunでmonorepo構成のためか、npm init playwright@latest
に失敗したので手動で設定したメモを残す。
準備
$ bun i -d @playwright/test @cucumber/cucumber ts-node
$ npx playwright install
ソースコード
{
"name": "@odyssage/frontend",
- "type": "module",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "bunx --bun vite",
"build": "tsc -b && bunx --bun vite build",
// 省略
+ "e2e-test": "cucumber-js --config cucumber.mjs --exit"
},
"dependencies": {
"@odyssage/core": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@cucumber/cucumber": "^11.0.1",
"@odyssage/eslint-config-custom": "workspace:*",
"@odyssage/tsconfig": "workspace:*",
"@playwright/test": "^1.48.2",
"ts-node": "^10.9.2",
// 省略
"vite": "^5.4.11"
}
}
"type": "module"
を指定しているとError: Cucumber expected a CommonJS module
が発生。
export default {
paths: ['**/features/*.feature'],
require: ['**/tests/*.steps.ts'],
requireModule : ['ts-node/register']
};
Feature: Character List Management
Background:
Given アプリが起動している
Scenario: キャラクターリストが空の場合
When キャラクターリストページを表示する
Then "キャラクターが見つかりませんでした" と表示される
Scenario: キャラクターを追加する
Given キャラクターリストが空である
When "John Doe" という名前のキャラクターを追加する
Then キャラクターリストに "John Doe" が表示される
Scenario: キャラクターを複数追加して表示する
Given キャラクターリストが空である
When 以下のキャラクターを追加する:
| name |
| Alice |
| Bob |
Then キャラクターリストに "Alice" と "Bob" が表示される
import { Given, When, Then, Before } from '@cucumber/cucumber';
import { expect, chromium, Page } from '@playwright/test';
let page: Page;
Before(async () => {
const browser = await chromium.launch({ headless: false }); // headless: true にするとブラウザが表示されない
const context = await browser.newContext();
page = await context.newPage();
});
Given('アプリが起動している', async () => {
await page.goto('http://localhost:5173');
});
Given('キャラクターリストが空である', async () => {
await page.evaluate(() => localStorage.clear());
await page.reload();
});
When('キャラクターリストページを表示する', async () => {
await page.click('text=キャラクターリスト');
});
Then('"キャラクターが見つかりませんでした" と表示される', async () => {
const message = await page.locator('text=キャラクターが見つかりませんでした');
await expect(message).toBeVisible();
});
When('{string} という名前のキャラクターを追加する', async (name) => {
await page.fill('input[placeholder="キャラクター名を入力"]', name);
await page.click('button:has-text("キャラクターを追加")');
});
Then('キャラクターリストに {string} が表示される', async (name) => {
const character = await page.locator(`text=${name}`);
await expect(character).toBeVisible();
});
When('以下のキャラクターを追加する:', async (table) => {
for (const { name } of table.hashes()) {
await page.fill('input[placeholder="キャラクター名を入力"]', name);
await page.click('button:has-text("キャラクターを追加")');
}
});
Then(
'キャラクターリストに {string} と {string} が表示される',
async (name1, name2) => {
const character1 = await page.locator(`text=${name1}`);
const character2 = await page.locator(`text=${name2}`);
await expect(character1).toBeVisible();
await expect(character2).toBeVisible();
},
);
import customConfig from '@odyssage/eslint-config-custom/frontend.js';
import tseslint from 'typescript-eslint';
export default tseslint.config({
extends: [
...customConfig,
+ {
+ files: ['**/tests/**'],
+ rules: {
+ 'import/extensions': ['off'],
+ 'import/no-extraneous-dependencies': ['off'],
+ 'import/no-unresolved': ['off'],
+ 'sonarjs/slow-regex': ['off'],
+ 'lintsonarjs/no-empty-test-file': ['off'],
+ 'no-restricted-syntax': ['off'],
+ 'no-await-in-loop': ['off'],
+ },
+ },
],
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
typescript: {
project: './tsconfig.app.json',
},
},
},
});
テスト実行
bun run dev
bun run e2e-test
2024.11.16 追記 "type": "module" を削除せずにESMで動作させる
もとのviteの構成をあまり変えたくない場合。
まずは commonJSからesmにcucumberの設定を変更。
export default {
paths: ['**/features/*.feature'],
- require: ['**/tests/*.steps.ts'],
- requireModule : ['ts-node/register']
+ import: ['**/tests/*.steps.ts'],
+ loader : ['ts-node/esm']
};
cucumberで使用するts-nodeがreferencesに対応していない ( *ts-node issue ) のでcompilerOptions:moduleでESM(Nodenext)を指定する。
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" },
],
+ "compilerOptions": {
+ "module": "NodeNext"
+ }
}
これで"type":"module"
をもとに戻しても大丈夫
{
"name": "@odyssage/frontend",
+ "type": "module",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "bunx --bun vite",
"build": "tsc -b && bunx --bun vite build",
// 省略
"e2e-test": "cucumber-js --config cucumber.mjs --exit"
},
"dependencies": {
"@odyssage/core": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@cucumber/cucumber": "^11.0.1",
"@odyssage/eslint-config-custom": "workspace:*",
"@odyssage/tsconfig": "workspace:*",
"@playwright/test": "^1.48.2",
"ts-node": "^10.9.2",
// 省略
"vite": "^5.4.11"
}
}
発生していたエラー
tsconfig.tsにreferences
をつけただけの時のエラー
$ bun run e2e-test
$ cucumber-js --config cucumber.mjs --exit
(node:25960) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
ReferenceError: exports is not defined in ES module scope
at file:///D:/projects/odyssage/packages/bdd-e2e-test/e2e/step-definitions/characterList.steps.ts:38:23
at ModuleJob.run (node:internal/modules/esm/module_job:268:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:543:26)
at async getSupportCodeLibrary (D:\projects\odyssage\node_modules\@cucumber\cucumber\lib\api\support.js:32:9)
at async runCucumber (D:\projects\odyssage\node_modules\@cucumber\cucumber\lib\api\run_cucumber.js:49:11)
at async Cli.run (D:\projects\odyssage\node_modules\@cucumber\cucumber\lib\cli\index.js:56:29)
at async Object.run [as default] (D:\projects\odyssage\node_modules\@cucumber\cucumber\lib\cli\run.js:29:18)
error: script "e2e-test" exited with code 1
loaderに ts-node/esm を cliオプションで指定した時のエラー --loader ts-node/esm
(node:4356) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
e2e/tests/characterList.steps.ts(1,43): error TS2792: Cannot find module '@cucumber/cucumber'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
e2e/tests/characterList.steps.ts(2,34): error TS2792: Cannot find module '@playwright/test'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
e2e/tests/characterList.steps.ts(3,27): error TS2792: Cannot find module '@playwright/test'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
参考
参考: CucumberとPlaywright+生成AIを使ってTypeScriptで自動テストを書こう!WebアプリやAndroidネイティブアプリもBDDで!
公式 : Installation
公式 : github
github - Playwright with Typescript - Cucumber - BDD
package.json で type: "module" を設定していると、TypeScript による Playwright のテストが、その実行時に構文エラー
Cucumber.js + TypeScriptではまった話
2024.11.16 追記
ViteとTypeScriptを5系にバージョンアップする
サバイバルTypescript - プロジェクト参照 (project references)
CompilerOptions - module