10日で覚えるCypressDay 5: ページ遷移とフォーム操作
books.chapter 510日で覚えるCypress

Day 5: ページ遷移とフォーム操作

今日学ぶこと

  • cy.visit() の詳細オプション(baseUrl, timeout, headers)
  • cy.url(), cy.location() でURL検証
  • cy.go(), cy.reload() でブラウザナビゲーション
  • フォーム要素の操作(input, select, checkbox, radio, textarea)
  • ファイルアップロード(cy.selectFile())
  • フォーム送信とバリデーションテスト

ページ遷移の基本

cy.visit() の詳細オプション

cy.visit() は単にURLを開くだけでなく、さまざまなオプションを指定できます。

// 基本的な使い方
cy.visit('https://example.com')

// 相対パス(baseUrlが設定されている場合)
cy.visit('/login')

// オプション付き
cy.visit('/dashboard', {
  timeout: 30000,          // タイムアウト(ms)
  failOnStatusCode: false, // ステータスコードでの失敗を無効化
  headers: {
    'Accept-Language': 'ja'
  },
  auth: {
    username: 'admin',
    password: 'secret'
  },
  qs: {
    page: 1,
    sort: 'name'
  }
})

baseUrl の設定

cypress.config.jsbaseUrl を設定すると、毎回フルURLを書く必要がなくなります。

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
  }
})
// baseUrl が設定されていれば相対パスで OK
cy.visit('/')          // http://localhost:3000/
cy.visit('/about')     // http://localhost:3000/about
cy.visit('/users/1')   // http://localhost:3000/users/1
flowchart LR
    Config["cypress.config.js\nbaseUrl設定"] --> Visit["cy.visit('/path')"]
    Visit --> Full["http://localhost:3000/path"]

    style Config fill:#3b82f6,color:#fff
    style Visit fill:#8b5cf6,color:#fff
    style Full fill:#22c55e,color:#fff

URL検証

cy.url() でURLを確認

// 現在のURLを検証
cy.visit('/login')
cy.url().should('include', '/login')
cy.url().should('eq', 'http://localhost:3000/login')
cy.url().should('match', /\/login$/)

cy.location() で詳細なURL情報を取得

// URL: http://localhost:3000/search?q=cypress#results

cy.location().should((loc) => {
  expect(loc.protocol).to.eq('http:')
  expect(loc.hostname).to.eq('localhost')
  expect(loc.port).to.eq('3000')
  expect(loc.pathname).to.eq('/search')
  expect(loc.search).to.eq('?q=cypress')
  expect(loc.hash).to.eq('#results')
})

// 特定のプロパティだけを検証
cy.location('pathname').should('eq', '/search')
cy.location('search').should('include', 'q=cypress')
コマンド 戻り値 用途
cy.url() フルURL文字列 URLの簡単な検証
cy.location() Locationオブジェクト URLの詳細な検証
cy.location('pathname') パス文字列 パスのみの検証
cy.location('search') クエリ文字列 クエリパラメータの検証
cy.location('hash') ハッシュ文字列 ハッシュフラグメントの検証

ブラウザナビゲーション

cy.go() で履歴を移動

// ページを遷移してから戻る
cy.visit('/page-1')
cy.visit('/page-2')
cy.visit('/page-3')

// 戻る
cy.go('back')
cy.url().should('include', '/page-2')

// さらに戻る
cy.go(-1)
cy.url().should('include', '/page-1')

// 進む
cy.go('forward')
cy.url().should('include', '/page-2')

// 2つ進む
cy.go(2)
cy.url().should('include', '/page-3') // ※ブラウザ履歴に依存

cy.reload() でページを再読み込み

// 通常のリロード
cy.reload()

// キャッシュを無視したリロード(ハードリロード)
cy.reload(true)
flowchart LR
    Back["cy.go('back')\ncy.go(-1)"] --> Current["現在のページ"]
    Current --> Forward["cy.go('forward')\ncy.go(1)"]
    Current --> Reload["cy.reload()"]
    Reload --> Current

    style Back fill:#f59e0b,color:#fff
    style Current fill:#3b82f6,color:#fff
    style Forward fill:#22c55e,color:#fff
    style Reload fill:#8b5cf6,color:#fff

フォーム要素の操作

テキスト入力(input, textarea)

// テキスト入力
cy.get('#username').type('testuser')
cy.get('#password').type('secret123')

// テキストエリア
cy.get('textarea#comment').type('これはコメントです。\n改行も入力できます。')

// 特殊キーの入力
cy.get('#search').type('Cypress{enter}')        // Enterキー
cy.get('#email').type('{selectall}{backspace}')  // 全選択して削除
cy.get('#field').type('{ctrl+a}')                // Ctrl+A

// 入力をクリアしてから入力
cy.get('#name').clear().type('新しい名前')

// 遅延を入れてタイプ(1文字あたり100ms)
cy.get('#slow-input').type('ゆっくり入力', { delay: 100 })

type() で使える特殊キー

キー 記法 説明
Enter {enter} Enterキー
Tab {tab} Tabキー
Escape {esc} Escapeキー
Backspace {backspace} Backspaceキー
Delete {del} Deleteキー
上矢印 {uparrow} 上方向キー
下矢印 {downarrow} 下方向キー
左矢印 {leftarrow} 左方向キー
右矢印 {rightarrow} 右方向キー
全選択 {selectall} 全テキスト選択

セレクトボックス(select)

// テキストで選択
cy.get('#country').select('Japan')

// value属性で選択
cy.get('#country').select('jp')

// インデックスで選択
cy.get('#country').select(2)

// 複数選択(multiple属性のselect)
cy.get('#skills').select(['JavaScript', 'TypeScript', 'Python'])

// 選択されている値を検証
cy.get('#country').should('have.value', 'jp')
// HTML例
// <select id="country">
//   <option value="">選択してください</option>
//   <option value="jp">Japan</option>
//   <option value="us">United States</option>
//   <option value="uk">United Kingdom</option>
// </select>

cy.get('#country').select('Japan')
cy.get('#country').should('have.value', 'jp')

チェックボックスとラジオボタン

// チェックボックスをオンにする
cy.get('#agree').check()
cy.get('#agree').should('be.checked')

// チェックボックスをオフにする
cy.get('#agree').uncheck()
cy.get('#agree').should('not.be.checked')

// 値を指定してチェック(同じname属性の複数チェックボックス)
cy.get('input[name="hobbies"]').check(['reading', 'coding'])

// ラジオボタンを選択
cy.get('input[name="gender"]').check('male')
cy.get('input[name="gender"]').should('have.value', 'male')

// force オプション(非表示要素のチェック)
cy.get('#hidden-checkbox').check({ force: true })
flowchart TB
    subgraph CheckboxOps["チェックボックス操作"]
        Check["cy.check()"] --> Checked["checked状態"]
        Uncheck["cy.uncheck()"] --> Unchecked["unchecked状態"]
    end

    subgraph RadioOps["ラジオボタン操作"]
        Radio["cy.check('value')"] --> Selected["選択状態"]
    end

    subgraph Verify["検証"]
        V1["should('be.checked')"]
        V2["should('not.be.checked')"]
        V3["should('have.value', 'x')"]
    end

    style CheckboxOps fill:#3b82f6,color:#fff
    style RadioOps fill:#8b5cf6,color:#fff
    style Verify fill:#22c55e,color:#fff

ファイルアップロード

cy.selectFile() の基本

// ファイルパスを指定してアップロード
cy.get('input[type="file"]').selectFile('cypress/fixtures/image.png')

// 複数ファイルのアップロード
cy.get('input[type="file"]').selectFile([
  'cypress/fixtures/file1.pdf',
  'cypress/fixtures/file2.pdf'
])

// ドラッグ&ドロップでアップロード
cy.get('.dropzone').selectFile('cypress/fixtures/data.csv', {
  action: 'drag-drop'
})

// Blobオブジェクトからアップロード
cy.get('input[type="file"]').selectFile({
  contents: Cypress.Buffer.from('file content'),
  fileName: 'test.txt',
  mimeType: 'text/plain',
  lastModified: Date.now()
})

fixture ファイルの準備

# テスト用のファイルを配置
cypress/
  fixtures/
    image.png       # テスト用画像
    data.csv        # テスト用CSV
    document.pdf    # テスト用PDF
    sample.json     # テスト用JSON

フォーム送信とバリデーションテスト

基本的なフォーム送信テスト

describe('ログインフォーム', () => {
  beforeEach(() => {
    cy.visit('/login')
  })

  it('正常にログインできる', () => {
    cy.get('#email').type('user@example.com')
    cy.get('#password').type('password123')
    cy.get('button[type="submit"]').click()

    cy.url().should('include', '/dashboard')
    cy.get('.welcome-message').should('contain', 'ようこそ')
  })

  it('メールアドレスが空の場合エラーが表示される', () => {
    cy.get('#password').type('password123')
    cy.get('button[type="submit"]').click()

    cy.get('#email-error')
      .should('be.visible')
      .and('contain', 'メールアドレスを入力してください')
  })

  it('無効なメールアドレスでエラーが表示される', () => {
    cy.get('#email').type('invalid-email')
    cy.get('#password').type('password123')
    cy.get('button[type="submit"]').click()

    cy.get('#email-error')
      .should('be.visible')
      .and('contain', '有効なメールアドレスを入力してください')
  })
})

会員登録フォームの総合テスト

describe('会員登録フォーム', () => {
  beforeEach(() => {
    cy.visit('/register')
  })

  it('すべてのフィールドを入力して送信できる', () => {
    // テキスト入力
    cy.get('#name').type('山田太郎')
    cy.get('#email').type('yamada@example.com')
    cy.get('#password').type('SecurePass123!')
    cy.get('#password-confirm').type('SecurePass123!')

    // セレクトボックス
    cy.get('#prefecture').select('東京都')

    // ラジオボタン
    cy.get('input[name="gender"][value="male"]').check()

    // チェックボックス
    cy.get('input[name="interests"]').check(['technology', 'music'])

    // テキストエリア
    cy.get('#bio').type('よろしくお願いします。')

    // ファイルアップロード
    cy.get('#avatar').selectFile('cypress/fixtures/avatar.png')

    // 利用規約に同意
    cy.get('#terms').check()

    // フォーム送信
    cy.get('form').submit()

    // 成功画面の確認
    cy.url().should('include', '/welcome')
    cy.get('.success-message').should('contain', '登録が完了しました')
  })

  it('パスワードが一致しない場合エラーが表示される', () => {
    cy.get('#password').type('SecurePass123!')
    cy.get('#password-confirm').type('DifferentPass456!')
    cy.get('form').submit()

    cy.get('#password-error')
      .should('be.visible')
      .and('contain', 'パスワードが一致しません')
  })
})

バリデーションパターン一覧

flowchart TB
    subgraph Input["入力"]
        I1["テキスト入力"]
        I2["選択"]
        I3["チェック"]
    end

    subgraph Validation["バリデーション"]
        V1["必須チェック"]
        V2["形式チェック"]
        V3["一致チェック"]
        V4["範囲チェック"]
    end

    subgraph Result["結果"]
        R1["成功\n画面遷移"]
        R2["エラー\nメッセージ表示"]
    end

    Input --> Validation
    Validation --> |"OK"| R1
    Validation --> |"NG"| R2

    style Input fill:#3b82f6,color:#fff
    style Validation fill:#f59e0b,color:#fff
    style R1 fill:#22c55e,color:#fff
    style R2 fill:#ef4444,color:#fff

まとめ

カテゴリ コマンド 用途
ページ遷移 cy.visit() ページを開く
URL検証 cy.url() 現在のURLを検証
URL検証 cy.location() URLの詳細情報を検証
ナビゲーション cy.go('back') 前のページに戻る
ナビゲーション cy.go('forward') 次のページに進む
ナビゲーション cy.reload() ページを再読み込み
テキスト入力 cy.type() テキストを入力
テキスト入力 cy.clear() テキストをクリア
セレクト cy.select() オプションを選択
チェック cy.check() チェックボックス/ラジオをオンに
チェック cy.uncheck() チェックボックスをオフに
ファイル cy.selectFile() ファイルをアップロード
フォーム .submit() フォームを送信

重要ポイント

  1. baseUrl を設定する - cypress.config.js で設定しておくと、テストコードがすっきりする
  2. cy.location() を活用する - URLの各部分を個別に検証できるため、より正確なテストが書ける
  3. clear() + type() のパターン - 既存の入力値をクリアしてから新しい値を入力する習慣をつける
  4. バリデーションテストは網羅的に - 正常系だけでなく、異常系(空、不正形式、境界値)もテストする
  5. selectFile() を使う - ファイルアップロードは専用コマンドで簡潔にテストできる

練習問題

基本

  1. cy.visit()qs オプションを使って、クエリパラメータ付きでページを開いてください
  2. ページ遷移後に cy.url()cy.location('pathname') でURLを検証してください
  3. テキスト入力に cy.clear()cy.type() を使って値を更新してください

応用

  1. セレクトボックス、チェックボックス、ラジオボタンを含むフォームの操作テストを作成してください
  2. パスワードの一致チェックを含むバリデーションテストを作成してください
  3. cy.go('back')cy.go('forward') を使ったナビゲーションテストを作成してください

チャレンジ

  1. 会員登録フォームの全フィールド(テキスト、セレクト、ラジオ、チェックボックス、ファイル)を操作する統合テストを作成してください
  2. バリデーションエラーの表示・非表示を網羅的にテストするスイートを作成してください

参考リンク


次回予告

Day 6では、ネットワークリクエストの制御を学びます。cy.intercept() を使ったAPIリクエストのインターセプト、レスポンスのスタブ、エラーシミュレーションなど、モダンなWebアプリのテストに欠かせないテクニックを習得しましょう。