CodeceptJS のテストフィルタ活用と ESLint カスタムルールによる安全対策

# はじめに
CodeceptJS では [Vitest](https://vitest.dev/guide/filtering) や [Playwright](https://playwright.dev/docs/test-annotations) のように特定のテストケースやスイートのみを実行したり、一時的にスキップしたりするための簡単な記法が用意されています。
[公式ドキュメント](https://codecept.io/basics/#skipping)に基づいてテストのフィルタリング方法を整理します。
また、フィルタリングした内容が誤って残ってしまうのを防ぐため、ESLint のカスタムルールを適用する方法を紹介します。
※カスタムルールの詳細な仕様や、Linter を開発フローにどう組み込むかの運用のお話はしませんので、ご了承ください。

<details><summary>注意事項</summary>

:::note warn
 記事掲載時点、CodeceptJS は ESModules にまだ対応していないようです。
 [Migrate CodeceptJS to ESM (Top-level await) · Issue #4185 · codeceptjs/CodeceptJS](https://github.com/codeceptjs/CodeceptJS/issues/4185)
 そのため、本記事内のコードでは基本 ESModules の記法に統一していますが、
 `import` → `require` など、適宜プロジェクトの状況によって読み替えてください。
:::

</details>


# 環境情報
OS: Windows 11
Visual Studio Code: 1.100.2
CodeceptJS: 3.7.3
ESLint: v9.27.0

# CodeceptJS のテストフィルタリング
## `.only`：特定のテストだけを実行
`Scenario.only` を使うと、**そのテストだけが実行対象**になります。

```js
Feature("ログイン機能");

Scenario.only("正しい認証情報でログインできること", ({ I }) => {
  I.amOnPage("/login");
  I.fillField("ユーザー名","user1");
  I.fillField("パスワード","password");
  I.click("ログイン");
  I.see("ようこそ");
});
```

この例では、他の Scenario は無視され、このテストだけが実行されます。
特に修正した部分のみを実行して確認するのに有用です。


## `.skip`：特定のテストをスキップ
`Scenario.skip` (`xScenario`) や `Feature.skip` (`xFeature`) を使うと、**一時的にそのテストを無効化**できます。

```js
Feature("ログイン機能");

Scenario.skip("ログイン失敗時のエラーメッセージ", ({ I }) => {
  // 現在修正中なのでスキップ
});
```

`.skip` を使うと、コメントアウトよりも意図が明確になり、元に戻しやすくなります。
※`Feature.skip` (`xFeature`)は CodeceptJS の v2.6.6 から


## その他：grep によるフィルタリング（コマンドライン）
CodeceptJS のコマンドには `--grep` オプションがあり、シナリオ名にマッチするものだけを実行できます。

```bash
npx codeceptjs run --grep "ログイン"
```

`Feature`, `Scenario` 両方とも検索にヒットします。
正規表現も使えるため、柔軟にフィルタリングが可能です。

※特に正規表現で指定しない場合は**部分一致**になるようなので注意が必要です。
"ログインの機能" で検索した場合、"ログインの機能1"、"ログインの機能2" のいずれもヒットします。


# ESLint カスタムルール
以下を目標として作成していきます。
- `Scenario.skip()` と `Scenario.only()` の使用を検出して警告する
- ESLint の自動修正機能でこれらを通常の `Scenario()` に変換する
- プラグインとしては公開しない（ローカルでルールを適用するのに留める）

## 1. カスタムルールファイルを作成する
まず、ESLint のカスタムルールを格納するディレクトリとルールファイルを作成します。
 `./eslint-rules/no-scenario-skip-or-only.js`

## 2. ルールのロジックを実装する
以下のコードを記述します。

```js:no-scenario-skip-or-only.js
export default {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow Scenario.skip and Scenario.only in CodeceptJS tests',
      recommended: true,
      url: null
    },
    fixable: 'code',
    schema: [],
    messages: {
      noScenarioSkip: "Unexpected 'Scenario.skip()'. Remove '.skip' to run all tests.",
      noScenarioOnly: "Unexpected 'Scenario.only()'. Remove '.only' to run all tests."
    }
  },
  create(context) {
    return {
      MemberExpression(node) {
        if (
          node.object.name === 'Scenario' &&
          (node.property.name === 'skip' || node.property.name === 'only')
        ) {
          context.report({
            node,
            messageId: node.property.name === 'skip' ? 'noScenarioSkip' : 'noScenarioOnly',
            fix(fixer) {
              // '.skip' または '.only' 部分を削除
              return fixer.removeRange([node.object.range[1], node.range[1]]);
            }
          });
        }
      }
    };
  }
};
```

補足として、`Scenario.skip` の部分は、AST（抽象構文木） 上では `MemberExpression` として表現されます。
- `Scenario` が「オブジェクト」
- `skip` が「プロパティ」

ノードタイプやセレクタについて詳しくは[公式ドキュメント](https://eslint.org/docs/latest/extend/selectors)を参照ください。

## 3. カスタムルールのテストを書く
カスタムルールのテストファイルを作成し、有効なテストケースと無効なテストケースに対してテストします。

```js:no-scenario-skip-or-only.test.js
import { RuleTester } from 'eslint';
import rule from '../no-scenario-skip-or-only.js';

const ruleTester = new RuleTester();

ruleTester.run('no-scenario-skip-or-only', rule, {
  valid: ['Scenario("xxx", () => {} )'],
  invalid: [
    {
      code: 'Scenario.skip("xxx", () => {} )',
      errors: [{ messageId: 'noScenarioSkip' }],
      output: 'Scenario("xxx", () => {} )'
    },
    {
      code: 'Scenario.only("xxx", () => {} )',
      errors: [{ messageId: 'noScenarioOnly' }],
      output: 'Scenario("xxx", () => {} )'
    }
  ]
});
console.log('All tests passed!');
```

`node eslint-rules/no-scenario-skip-or-only.test.js` で実行します。
テストに合格するとコンソールに `All tests passed!` が出力されます。
## 4. ESLint 設定ファイルにカスタムルールを追加する
`eslint.config.js` ファイルを以下のように更新します（Flat Config 使用時）。

```js:eslint.config.js
// ...
// カスタムルールを読み込み
import noScenarioSkipOrOnlyRule from './eslint-rules/no-scenario-skip-or-only.js';

// カスタムプラグインの定義
const codeceptRules = {
  plugins: {
    codecept: {
      rules: {
        'no-scenario-skip-or-only': noScenarioSkipOrOnlyRule
      }
    }
  }
};

export default [
  // ...
  codeceptRules, // カスタムプラグイン設定を追加
  {
    rules: {
      // カスタムルールを適用
      'codecept/no-scenario-skip-or-only': 'error'
    }
  },
];
```

※「カスタムプラグインの定義」の部分に関しては別途プラグイン用のファイルを作成することでルールを npm パッケージで共有し、他のプロジェクトで使用できるようになります。
　今回はローカルでの使用にとどめます。
### 5. ESLint を実際に実行して確認する
以下のような簡易的な CodeceptJS 用のテストファイルを作成します。
`./sample.test.js`
※意図的に `Scenario.skip` と `Scenario.only` を含めてルールが動作するか確認します。
```js:sample.test.js
Feature('My First Test');

Scenario('test something', ({ I }) => {
  I.amOnPage('https://github.com');
  I.see('GitHub');
});

Scenario.skip('skip', () => {}); // 検出される

Scenario.only('only', () => {}); // 検出される
```

:::note
VS Code に ESLint の拡張機能を導入している場合は、この時点でエラーが検出されるとおもいます。
![Pasted image 20250521060324.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109619/7a06d766-c615-4b8d-96b8-47a7c23b9641.png)
:::


次のコマンドを実行してルールが機能するか確認します。
`npx eslint sample.test.js`

エラーメッセージが表示されれば、ルールが正常に機能しています。
```terminal
sample.test.js:8:1: Unexpected 'Scenario.skip()'. Remove '.skip' to run all tests. codecept/no-scenario-skip-or-only

sample.test.js:10:1: Unexpected 'Scenario.only()'. Remove '.only' to run all tests. codecept/no-scenario-skip-or-only
```

## 6. 自動修正を試す
`--fix` オプションを付けて ESLint を実行すると、`.skip` と `.only` が自動的に削除されます。

`npx eslint sample.test.js --fix`

自動修正後、テストファイルは以下のように変わります。
```diff_javascript:sample.test.js
Feature('My First Test');

Scenario('test something', ({ I }) => {
  I.amOnPage('https://github.com');
  I.see('GitHub');
});

- Scenario.skip('skip', () => {});
+ Scenario('skip', () => {});

- Scenario.only('only', () => {});
+ Scenario('only', () => {});
```


# まとめ
`.only`、`.skip` を活用することで、テストの作成や確認を効率化できます。
また、カスタムルールをプロジェクトに追加することで、開発者が誤って `Scenario.skip` や `Scenario.only` を残してしまうミスを防止できます。※
※コミット時は　Husky と lint-staged を、PRマージ時であれば CI/CD パイプラインに組み込むなど。

# 参考リンク
[CodeceptJS公式ドキュメント：Skipping](https://codecept.io/basics/#skipping)
[Custom Rule Tutorial - ESLint - Pluggable JavaScript Linter](https://eslint.org/docs/latest/extend/custom-rule-tutorial)