第5章:ランナー詳細編

runnの特徴の一つは、複数のプロトコルを統一的に扱えることです。この章では、各ランナーの使い方を詳しく説明します。

runnが対応するランナー

runnは6つのランナーを提供しています。それぞれが特定のプロトコルに対応します。

ランナー プロトコル 用途
HTTP HTTP/HTTPS REST API、GraphQL、Webhookのテスト
gRPC gRPC マイクロサービス間通信のテスト
DB SQL データベース操作のテスト
CDP Chrome DevTools Protocol ブラウザ自動化
SSH SSH リモートサーバーの操作
Exec プロセス実行 ローカルコマンドの実行

HTTPランナー

基本的な設定

desc: HTTPランナーの基本設定
runners:
  api: https://api.example.com/v1
  # 複数のエンドポイントを定義可能
  auth: https://auth.example.com
  webhook: http://localhost:8080/webhook

リクエストメソッドとパラメータ

desc: HTTPリクエストメソッドとパラメータの例
runners:
  httpbin: http://localhost:8080
steps:
  # GET リクエスト
  get_request:
    httpbin:
      /get?page=1&limit=10&sort=created_at:
        get:
          headers:
            Accept: application/json
            User-Agent: runn/test
    test: |
      current.res.status == 200 &&
      current.res.body.args.page[0] == "1" &&
      current.res.body.args.limit[0] == "10" &&
      current.res.body.args.sort[0] == "created_at"

  # POST リクエスト
  post_request:
    httpbin:
      /post:
        post:
          headers:
            Content-Type: application/json
          body:
            application/json:
              name: "Alice"
              email: "alice@example.com"
              role: "user"
    test: |
      current.res.status == 200 &&
      current.res.body.json.name == "Alice" &&
      current.res.body.json.email == "alice@example.com" &&
      current.res.body.json.role == "user"

  # PUT リクエスト
  put_request:
    httpbin:
      /put:
        put:
          body:
            application/json:
              name: "Alice Smith"
              email: "alice.smith@example.com"
              id: 123
    test: |
      current.res.status == 200 &&
      current.res.body.json.name == "Alice Smith" &&
      current.res.body.json.email == "alice.smith@example.com"

  # PATCH リクエスト
  patch_request:
    httpbin:
      /patch:
        patch:
          body:
            application/json:
              role: "admin"
    test: |
      current.res.status == 200 &&
      current.res.body.json.role == "admin"

  # DELETE リクエスト
  delete_request:
    httpbin:
      /delete:
        delete: {}
    test: current.res.status == 200

  # Bearer認証の例
  auth_request:
    httpbin:
      /bearer:
        get:
          headers:
            Authorization: "Bearer test-token-123"
    test: |
      current.res.status == 200 &&
      current.res.body.authenticated == true &&
      current.res.body.token == "test-token-123"

様々なボディ形式

desc: 様々なボディ形式のHTTPリクエスト
runners:
  httpbin: http://localhost:8080
steps:
  # JSON形式
  json_request:
    httpbin:
      /post:
        post:
          body:
            application/json:
              key: "value"
              nested:
                array: [1, 2, 3]
    test: |
      current.res.status == 200 &&
      current.res.body.headers["Content-Type"][0] == "application/json" &&
      current.res.body.json.key == "value" &&
      current.res.body.json.nested.array[0] == 1 &&
      current.res.body.json.nested.array[1] == 2 &&
      current.res.body.json.nested.array[2] == 3

  # フォームデータ
  form_request:
    httpbin:
      /post:
        post:
          body:
            application/x-www-form-urlencoded:
              username: alice
              password: secret123
    test: |
      current.res.status == 200 &&
      current.res.body.headers["Content-Type"][0] == "application/x-www-form-urlencoded" &&
      current.res.body.form.username[0] == "alice" &&
      current.res.body.form.password[0] == "secret123"

  # マルチパートフォーム(テストデータ用の文字列フィールドのみ)
  multipart_request:
    httpbin:
      /post:
        post:
          body:
            multipart/form-data:
              field1: "test value"
              description: "Test multipart form"
    test: |
      current.res.status == 200 &&
      current.res.body.headers["Content-Type"][0] contains "multipart/form-data" &&
      current.res.body.form.field1[0] == "test value" &&
      current.res.body.form.description[0] == "Test multipart form"

  # プレーンテキスト
  text_request:
    httpbin:
      /post:
        post:
          headers:
            Content-Type: text/plain
          body:
            text/plain: |
              This is a plain text message
              with multiple lines
    test: |
      current.res.status == 200 &&
      current.res.body.headers["Content-Type"][0] == "text/plain" &&
      current.res.body.data contains "This is a plain text message" &&
      current.res.body.data contains "with multiple lines"

  # カスタムヘッダーとJSONボディ
  custom_headers_request:
    httpbin:
      /anything:
        put:
          headers:
            X-Custom-Header: "custom-value"
            Authorization: "Bearer token123"
          body:
            application/json:
              action: "update"
              id: 42
    test: |
      current.res.status == 200 &&
      current.res.body.method == "PUT" &&
      current.res.body.headers["X-Custom-Header"][0] == "custom-value" &&
      current.res.body.headers["Authorization"][0] == "Bearer token123" &&
      current.res.body.json.action == "update" &&
      current.res.body.json.id == 42

gRPCランナー

TODO: grpc の例を追加する

データベースランナー

対応データベース

desc: 対応データベースの接続設定
runners:
  # PostgreSQL
  postgres: postgres://user:password@localhost:5432/testdb?sslmode=disable

  # MySQL
  mysql: mysql://user:password@localhost:3306/testdb

  # SQLite
  sqlite: sqlite:///path/to/database.db

  # Cloud Spanner
  spanner: spanner://projects/my-project/instances/my-instance/databases/my-db

基本的なクエリ操作

desc: 基本的なデータベースクエリ操作
runners:
  mydb:
    dsn: sqlite:///tmp/db_basic_queries.db
    trace: true

vars:
  test_email: "test@example.com"

steps:
  create_table:
    mydb:
      query: |
        DROP TABLE IF EXISTS users;
        CREATE TABLE users (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL,
          email TEXT NOT NULL UNIQUE,
          active BOOLEAN DEFAULT true,
          password_hash TEXT NOT NULL,
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )

  # INSERT クエリ
  insert_user:
    mydb:
      query: |
        INSERT INTO users (name, email, password_hash)
        VALUES ('{{ faker.Username() }}', '{{ faker.Email() }}', '{{ toBase64(faker.Password(true, true, true, false, false, 12)) }}')
    dump: current
    test: |
      current.last_insert_id == 1

  # SELECT クエリ
  select_users:
    mydb:
      query: |
        SELECT id, name, email, created_at
        FROM users
    test: |
      len(current.rows) <= 10

  # UPDATE クエリ
  update_user:
    mydb:
      query: |
        UPDATE users
        SET name = 'Updated name', updated_at = CURRENT_TIMESTAMP
        WHERE id = {{ steps.insert_user.last_insert_id }}
    dump: current
    test: |
      current.rows_affected == 1

  # DELETE クエリ
  delete_user:
    mydb:
      query: |
        DELETE FROM users
        WHERE id = {{ steps.insert_user.last_insert_id }}
    test: current.rows_affected == 1

CDPランナー(ブラウザ自動化)

主なCDP actions一覧

アクション名 概要
attributes 要素の属性取得
click 要素をクリック
doubleClick 要素をダブルクリック
evaluate JS式の評価
fullHTML ページ全体のHTML取得
innerHTML 要素のinnerHTML取得
localStorage localStorage取得
location 現在のURL取得
navigate 指定URLへ遷移
outerHTML 要素のouterHTML取得
screenshot スクリーンショット取得
scroll 要素までスクロール
sendKeys 要素にキー入力
sessionStorage sessionStorage取得
setUploadFile ファイルアップロード
setUserAgent User-Agent設定
submit フォーム送信
tabTo タブ切り替え
text 要素のテキスト取得
textContent 要素のtextContent取得
title ページタイトル取得
value 要素のvalue取得
wait 指定時間待機
waitReady 要素の準備完了まで待機
waitVisible 要素の表示まで待機

※詳細・最新情報は公式READMEをご参照ください。

基本的な使い方

desc: ブラウザ自動化テストの基本
runners:
  cdp: chrome://new  # 新しいChromeインスタンスを起動

steps:
  # ページナビゲーション
  navigate_to_page:
    cdp:
      actions:
        - navigate: https://pkg.go.dev/time
        - click: 'body > header > div.go-Header-inner > nav > div > ul > li:nth-child(2) > a'
        - waitVisible: 'body > footer'
        - text: 'h1'
    dump: current
    test: |
      current.text == 'Install the latest version of Go'

SSHランナー

基本的な設定

desc: SSH経由でのリモート操作
runners:
  server: ssh://user@example.com:22
steps:
  # ホスト名を表示
  show_hostname:
    ssh:
      server:///
        command: hostname
    test: current.exit_code == 0
    dump: current.stdout

Execランナー(ローカルコマンド実行)

exec ランナーは、コマンドを実行します。

desc: ローカルコマンドの実行

steps:
  # 基本的なコマンド実行
  basic_exec:
    exec:
      command: echo "Hello, World!"
    test: |
      current.exit_code == 0 &&
      current.stdout == "Hello, World!\n"