mcpField
Q
quicke2e

quicke2e

github.com/dmokaテスト・品質10574 回インストール5 週間前に更新
リポジトリ

quicke2e について

quicke2eは、Playwrightを使ったブラウザーE2Eテストのコマンドラインワークフローです。テストケースの設計とブラウザー実行を分離し、合否はAIモデルではなくコードで判定します。

主な機能

  1. 01ページ、フォーム、リンクの検出
  2. 02ブラウザーフロー仕様の作成
  3. 03Playwrightによるフロー実行
  4. 04弱いアサーションの検査
  5. 05境界・拒否・攻撃ケースのテスト

活用シーン

  1. 01ブラウザーテスト前のアプリ構造把握
  2. 02ロール別のログイン後フロー検証
  3. 03フォーム検証と業務ルールのテスト
  4. 04攻撃的な入力への拒否動作の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dmoka/quicke2e --skill quicke2e

インストール後、Claude Code で/quicke2eを入力して読み込まれたか確認します。

詳細

quicke2e — ケースを考案し、Jev に操作させ、コードで判定する

役割を分担します。曖昧にしないでください:

  • **あなた(大規模モデル)**はケースを考案し、仕様ファイルを作成します。一度だけ、オフラインで行います。
  • Jevは各仕様に従ってブラウザーを操作します。低コストで高速に、何度でも実行できます。Jev が選ぶのは、ツールが提示する選択肢のいずれかだけです。
  • コードが合格か不合格かを判定します。あなたでも JEV でもありません。

1. アプリをマッピングする

ベース URL が分からない場合は、尋ねてください。その後:

npx quicke2e discover http://localhost:3000 --start / -o quicke2e.map.json
  • localhost では、既定で全ページをクロールします。フォームを送信します。使い捨てデータベースを使用してください(シードデータを復元するには --reset "<cmd>" を使用します)。
  • それ以外のホストでは、ユーザーがデータを破棄してよいと確認しない限り、--safe を使用します(--i-own-this-data)。
  • サインインが必要なページ:まずセッションを保存し(以下のログイン手順)、--storage state.json を渡します。ロールごとに1回、サインアウト状態でも1回クロールします。サインイン済みのクロールでは /login や /join に到達しません。
  • マップを確認します:ページ、エッジ、フォーム、フィールド、選択肢、および各フォームの送信先。

ログインが必要なページ。 ログイン済みのブラウザー状態を一度保存し、その状態をクロールとすべてのフローで使用します:

// login.mjs: `TEST_EMAIL=... TEST_PASSWORD=... node login.mjs` writes auth.json (credentials from env, never in the spec)
import { chromium } from "playwright";
const b = await chromium.launch(); const p = await b.newPage();
await p.goto("http://localhost:3000/sign-in");
await p.getByLabel("Email").fill(process.env.TEST_EMAIL);
await p.getByLabel("Password").fill(process.env.TEST_PASSWORD);
await p.getByRole("button", { name: /sign in/i }).click();
await p.waitForURL((u) => !u.pathname.includes("sign-in"));
await p.context().storageState({ path: "auth.json" }); await b.close();
  • その状態でクロールします:npx quicke2e discover http://localhost:3000 --storage auth.json。これを指定しないと、マップにはサインアウト状態のページしか含まれません。
  • ログインが必要な各フローで、storageState: "auth.json" を指定します。ロールごとに1つのファイルを使います(customer.json、admin.json)。
  • データベースをリセットするたびに login.mjs を再実行します。シードデータの再投入でセッションが削除されるため、古い auth.json を使うと、実行のたびにサインインページへ移動します。
  • ログインフォームの前に Cookie バナーやウェルカムダイアログが表示される場合は、フォームに入力する前に login.mjs で同意するか閉じます。保存した auth.json にも同意状態が記録されるため、実行時にバナーが表示されなくなります。
  • storageState は sessionStorage を保持しません。アプリがそこにセッションやカートを保存する場合は、フロー内でログインしてください。
  • getByLabel("Password") が2つの要素(「パスワードを表示」ボタンなど)に一致する場合は、getByLabel("Password", { exact: true }) を使います。

2. ソースコードのルールを確認する

マップからは場所が分かります。満たすべき条件はソースコードに記載されています。次の順に読みます: ドメイン/ビジネスロジック(価格、上限、期間、権限、状態遷移)、バリデーションスキーマ、 API/ルートハンドラー、シードデータ(入力値として使える実際の名前、コード、日付)、README/PRD。 すべてのルールを1行ずつ列挙します:rule — file:line。

3. ケースを考案する

各フォームと各ルールについて、求められなくても、毎回、4 種類すべてのケースを作成します。

  1. 正常系 — ページの主要な機能が動作します。必ず最初に作成します。
  2. 境界値 — ルールの境界です。最終有効日、最大数量、上限のコードなどです。
  3. 拒否 — ルールに従って拒否する必要があります。期限切れの期間、売り切れ、誤ったコード、開始済みのイベント、ロールを持たないユーザーなどです。
  4. 攻撃 — 以下を参照してください。

攻撃:常に攻撃者の視点でも考えます。 求められなくても、毎回、攻撃ケースも考案します。攻撃者、カオスエンジニア、シニア QA エンジニアの視点で考えてください。スクリプト化されたテストスイートには決してないケースを、QuickE2E で実行可能な範囲で考案します。

  • 実際のフィールドに入力する不正な値:負数、ゼロ、極端に大きな数量、長すぎる文字列や Unicode 文字列、スクリプトタグ、SQL のような文字列、コードの余分な空白や不規則な大文字・小文字。
  • 割引コードの悪用:使用済みのコードの再使用、最初のコードに重ねて 2 つ目のコードを適用、期限切れまたは無効化されたコードを不規則な大文字・小文字で入力。
  • URL 経由で他のユーザーのリソースにアクセス:start は他人の注文、control はユーザー自身の注文です。
  • start の URL を細工:不自然なクエリ値(?qty=-3)、重複パラメーター(?code=A&code=B)、別形式の ID(/orders/0x2)。
  • 締め切られた期間:すでに開始したイベント、売り切れの公演。
  • URL 経由で完了済みのフローを再送信。
  • 必須フィールドを空欄のままにする、またはでたらめな値を入力する。

すべての攻撃ケースで、2 つの点を検証します。アプリが拒否すること:ユーザーに表示される拒否メッセージまたはエラー状態を expect に指定します。成功状態が存在しないこと:成功時にのみ表示されるテキスト(確認メッセージ、割引行、返金行など)を expectAbsent に指定します。ページ読み込み時から存在するラベルを指定してはいけません。拒否メッセージだけでは不十分です。アプリがエラーを表示しながら、割引を適用する可能性もあるためです。expectAbsent のテキストが表示されると、実行は結果 ABSENT_SEEN で停止します。これはアプリが攻撃を受け入れたことを意味します。エラーページ(500)が表示された場合もテストは失敗します。拒否メッセージが表示されないためです。

各攻撃ケースの spec に kind: "attack" を指定します。スクリプト化されたテストスイートにはないケースを優先します。既存のテストを先に確認し、どのルールがテストされていないかを記述してください。

攻撃ケースの制限
  • ブラウザーは 1 つ、アクションは一度に 1 つです。同時にダブルクリックする操作、競合状態、複数タブのフローは対象外です。
  • ヘッダー、Cookie、リクエストボディの改ざん、およびネットワークレベルの攻撃は行いません。start の URL の細工は対象です。
  • これらは API のプロパティテストまたはコードレベルの敵対的テスターの担当です。レポートでは、そのレイヤーでのケースとして記載してください。
  • 自分が所有するアプリだけを攻撃してください。使い捨てデータベースを使う localhost、またはユーザーが --i-own-this-data で所有を確認したホストが対象です。

4. spec を作成する

quicke2e.spec.mjs という 1 つのファイルで、配列をエクスポートします。

export default [
  {
    name: "refund-refused-after-start",
    kind: "attack",
    start: "/orders",
    maxSteps: 14,
    inputs: { email: "[email protected]" },          // every typed value comes from here
    goal: "Open the order for the concert that already started and request a refund",
    expectUrl: "/orders/\\d+$",                        // THE ASSERTION
    expect: ["Refunds close when the event starts"],   // THE ASSERTION
    neverClick: ["Confirm refund*"],                   // a refusal test must not complete the action
    expectAbsent: ["Refunded €"],                      // THE ASSERTION: the success state stays absent
  },
  {
    name: "other-users-order",                         // a load attack: opening the URL IS the attack
    kind: "attack",
    start: "/orders/1",                                // someone else's order
    control: "/orders/281",                            // the user's own order: the assertion must be false here
    goal: "Open order 1",
    expect: ["Nothing here"],
    expectAbsent: ["Total paid"],
  },
];

ページでサインイン済みユーザーが必要な場合は、各 spec に storageState: "auth.json" を設定します(ログインの手順はセクション 1 を参照してください)。シード済みのデモアカウントを使用します。 アサーションのルール — アサーション自体がテストです:

  • すべてのアサーションが成立する最初のスナップショットで実行が停止します。最後のアクションの後のページをアサートしてください(送信後なら、送信によって遷移したページ)。そうしないと、そのページに到達する前に実行が停止します。
  • 作業の完了後に真となることだけをアサートしてください。事前には存在し得ない URL、事前には存在しなかったテキスト、または expectState: [{ role, name, value | checked | selected }] によるコントロールの状態を指定します。
  • expect は、視覚的に確認できるユーザー向けのテキストです。フォームコントロールの内容(テストが入力または選択した値は結果ではありません)と非表示のテキストは無視されます。選択されたオプションやフィールドの値を確認するには、expectState を使ってください。消えるトーストを確認するには、expectSeen を使ってください。
  • もともとページにあるラベル、ボタンのテキスト、単語は絶対にアサートしないでください。
  • 削除の場合:expectGone: ["<the item's text>"](開始ページにあってもかまいませんが、最後には消えている必要があります)に加えて、空の状態または成功トーストを指定します。expectAbsent は、決して表示されてはならないテキストに使います。
  • expect は大文字と小文字を区別しません。アプリが大文字・小文字を正規化すること(小文字で保存されたメールアドレスなど)をテストするには、expectState でコントロールの値をアサートするか、その値だけが表示されるページで保存値をそのままアサートしてください。
  • スペースだけの値(" ")は、そのまま入力されます。空文字列("")は「フィールドを空のままにする」という意味です。
  • データを作成する仕様(サインアップ、新しいアイテムなど)では、実行ごとに新しい値を使うか、リセットする必要があります。--reset を使い、生成する値は一意にしてください(Date.now() は quicke2e プロセスごとに一度評価されます)。
  • 拒否のテストでは、拒否のテキストと成功状態が存在しないことの両方をアサートしてください。expectAbsent には、成功時にしか表示されないテキストを指定します。開始ページにすでに存在する expectAbsent のテキストは check に拒否されます(ABSENT_ON_START)。
  • 負荷攻撃(開始 URL 自体が攻撃となるケース)では、control が必要です。これは、アプリが受け入れるページです。その後 check は、コントロールページに対して WEAK_ASSERTION チェックを実行し、各 expectAbsent のテキストがコントロールページに実際に表示されることを要求します(成功マーカーが実在する必要があります)。実行は開始ページで判定し、クリックはしません。拒否が確認できなければ、結果は NOT_REFUSED です。
  • アプリが受け入れる可能性のある値(名前フィールド内のスクリプトタグなど)は、拒否のテストではありません。次のページで、その値がプレーンテキストとして表示されることをアサートしてください。
  • 目標には攻撃値とすべての操作を記述してください(「チケットの名前をスペースだけに置き換えてから、支払う」など)。「指定された値」では、エンジンはフィールドに入力しませんでした(測定結果:MODEL_BLOCKED 2/2。値を明記した場合は 2/2 成功)。
  • 拒否のテストにはすべて、成功につながる操作を neverClick に指定してください("Pay*"、"Place order*"、"Delete*")。アプリが誤って受け入れた場合や、ブラウザーが先にフォームをブロックした場合でも、購入、支払い、削除を行う前に実行が失敗します。測定結果:これを指定しなかった拒否のテストで、実際に注文が作成されました。例外:アプリがそのクリック時に拒否する場合(「Place order」の背後にあるサーバー側チェックなど)は、そのクリック自体がテストなので、指定しないでください。代わりに使い捨てのデータベースを使用してください。neverClick hid: "..." を含む失敗結果は、この状況が発生したことを示します。
  • ブラウザーの検証(min、max、required、type=email)は、アプリが入力を受け取る前に送信をブロックします。また、その吹き出しはページテキストではありません。このような拒否のテストを書く前に、ソースでこれらの属性を確認してください。この種のルールは通常、より低いレベルでテストします。
  • ドロップダウンの値も inputs から指定します。キーにはラベルの単語を、値には表示されているオプションのテキストを指定します。
  • inputs のフィールドキーはフィールドラベルに一致させる必要があります(「email」は「Email」に一致します)。ラベルに同じ部分文字列が含まれる場合(「Email」と「Billing email」など)、同じ値が適用されます。このようなフィールドには異なるキーを指定するか、このケースを避けてください。

ページに、エンジンに見せてはならない機密データ(リカバリーコード、保存済みカード、個人データなど)が表示される場合は、仕様で宣言してください。redact: [".backup-code", "#saved-cards", /recovery code \S+/i] (CSS セレクターとテキストパターン)。自動的に推測されることはありません。仕様に含まれる機密入力(パスワード、API キー、カード番号、トークンなど)は常に除外されます。 スペック間のデータ。 データ(注文やキャンセル済みのイベントなど)を変更するスペックがあると、次のスペックが目にする内容も変わります。毎回実行前に run --reset "<cmd>" を使ってシードデータを復元してください(たとえば --reset "npm run db:seed && node login.mjs")。または、スペック同士が干渉しないよう順序を調整してください。古い auth.json があると、AUTH_REQUIRED で実行がすぐに終了します。

ダイアログ、ホバー、ドラッグ。 ネイティブの confirm/prompt ダイアログは処理されます(prompt では、メッセージに名前が示されたキーのスペック値が使われます)。キャンセル経路をテストするには、dialog: "dismiss" を設定してください。ホバーでのみ表示されるコンテンツやドラッグ&ドロップは、ページが認識可能なパターンを使っている場合に対応します。目標は、その操作を表す言葉で記述してください(「user2 のアバターにカーソルを合わせる」「カードを Done にドラッグする」など)。

5. 確認してから実行

npx quicke2e check quicke2e.spec.mjs --base http://localhost:3000     # rejects WEAK_ASSERTION specs
npx quicke2e run quicke2e.spec.mjs --base http://localhost:3000 --map quicke2e.map.json --runs 3

WEAK_ASSERTION は、開始ページですでにアサーションが成立していることを意味します。アサーションを書き直してください。解消するために --allow-weak を追加してはいけません。

まずハッピーパスを実行してください(専用のファイルに分けるか、--only を使ってください)。ハッピーパスが失敗した場合は、その結果を報告し、成功するまでそのフローの境界、拒否、攻撃のケースは保留してください。壊れたフローでは、攻撃が拒否と障害を区別できません。

6. 報告

失敗したスペックごとに、APP のバグ(ソース内のルールに違反している)なのか、SPEC/TOOL の問題(実行が目的の場所に到達していない)なのかを判断してください。まず理由から判断します。各 FAIL の下の行(または --json のフィールド assertions、heldBack、loop、emptyRequired)には、成立しなかったアサーション、どのフィールドを対象にどの送信が保留されたか、どのアクションが繰り返されたかが示されています。次にトレース(--trace runs/)を読みます。各ステップには、エンジンが提示された操作(offered)が一覧表示されます。結果の意味は次のとおりです。DONE_VERIFIED = 合格、ABSENT_SEEN = アプリに成功時のみ表示されるテキストが表示され、攻撃を受け入れた(記録フィールドは absentSeen)、SERVER_ERROR = ページが HTTP 5xx を返した(記録フィールドは httpStatus)ため、アプリのバグです。自分で URL を開いてエラーを確認してください。MAX_STEPS/MODEL_BLOCKED/LOOP = 実行が目的の場所に到達していません。成功テキストが表示されたまま最終ページが間違っている場合は、アプリがルールに違反しています。

  • スペックを編集して合格させてはいけません。 拒否または攻撃のケースが失敗した場合、それが調査結果です。
  • アプリのバグごとに、ルール(ファイル名と行番号)、スペック名、発生したこと(結果、absentSeen、最終 URL)を報告してください。問題を検出できる最小単位のテストとして、ドメインルールのユニットテストか、ルートの API テストを提案してください。
  • 攻撃が途中で失敗した場合(MAX_STEPS、MODEL_BLOCKED、NO_SPEC_VALUE)、判定はありません。そのように記載し、合格やバグとして扱わないでください。
  • まずハッピーパスを報告し、その後に境界、拒否、攻撃の結果を報告してください。
  • 上記の制限により実行しなかった攻撃を、そのテストを行うべきレイヤーとともに一覧にしてください。
  • 常に成功させる必要があるフローでは、成功した実行から Playwright のスペックを生成してください。--emit e2e/generated/ を使います。

コメント