quicke2e について
quicke2eは、Playwrightを使ったブラウザーE2Eテストのコマンドラインワークフローです。テストケースの設計とブラウザー実行を分離し、合否はAIモデルではなくコードで判定します。
主な機能
- 01ページ、フォーム、リンクの検出
- 02ブラウザーフロー仕様の作成
- 03Playwrightによるフロー実行
- 04弱いアサーションの検査
- 05境界・拒否・攻撃ケースのテスト
活用シーン
- 01ブラウザーテスト前のアプリ構造把握
- 02ロール別のログイン後フロー検証
- 03フォーム検証と業務ルールのテスト
- 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 種類すべてのケースを作成します。
- 正常系 — ページの主要な機能が動作します。必ず最初に作成します。
- 境界値 — ルールの境界です。最終有効日、最大数量、上限のコードなどです。
- 拒否 — ルールに従って拒否する必要があります。期限切れの期間、売り切れ、誤ったコード、開始済みのイベント、ロールを持たないユーザーなどです。
- 攻撃 — 以下を参照してください。
攻撃:常に攻撃者の視点でも考えます。 求められなくても、毎回、攻撃ケースも考案します。攻撃者、カオスエンジニア、シニア 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/を使います。