Cypress Commandsの"next":非同期処理とキューシステムを理解して使いこなす


非同期処理とキューシステム

Cypress コマンドは非同期で実行されます。つまり、コマンドが実行されるとすぐに次の行に進まず、コマンドが完了するまで待機します。この非同期処理を管理するために、Cypress は内部的にキューシステムを使用します。キューシステムは、実行待ちのコマンドを順番に保持し、処理の準備が整った順に実行します。

コマンドチェーンと next

複数のコマンドを連続して実行したい場合は、コマンドチェーンと呼ばれる構文を使用します。コマンドチェーンは、. 演算子で連結された一連のコマンドで構成されます。例えば、以下のコードは、要素を取得し、その要素に値を入力し、最後にその要素のアサーションを行うコマンドチェーンを表します。

cy.get('input[type="text"]').type('Hello, world!').should('have.value', 'Hello, world!');

このコマンドチェーンでは、cy.get() コマンドは要素を取得し、その結果を次の cy.type() コマンドに渡します。同様に、cy.type() コマンドの結果は cy.should() コマンドに渡されます。

ここで next 関数の役割が登場します。next 関数は、コマンドチェーン内の次のコマンドをキューに追加します。しかし、その前に、現在実行中のコマンドを完了させます。つまり、next 関数は、コマンドチェーン内の次のコマンドに進む前に、前のコマンドが完了するのを確実にします。

next の具体的な使用方法

next 関数は、主に以下の 2 つのシナリオで使用されます。

  • 非同期処理の完了を待つ場合

非同期処理が完了するのを待たずに次のコマンドに進みたい場合は、next 関数を使用します。例えば、以下のコードは、非同期 API 呼び出しを行い、その結果を次のコマンドに渡す例です。

cy.request('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => {
    const postData = response.body;
    // ...
    next(postData);
  });

この例では、cy.request() コマンドは非同期的に API を呼び出し、その結果を response 変数に格納します。その後、then() コールバック内で postData 変数を next 関数に渡します。これにより、next 関数は、API 呼び出しが完了してから次のコマンドに postData を渡します。

  • テストの進行を制御する場合

テストの進行を制御するために next 関数を使用することもできます。例えば、以下のコードは、条件に応じてテストのフローを分岐する例です。

cy.get('button').click()
  .then(() => {
    if (someCondition) {
      next();
    } else {
      // ...別の処理を実行
    }
  });

この例では、cy.get('button').click() コマンドはボタンをクリックし、その結果を then() コールバックに渡します。その後、then() コールバック内で someCondition 変数をチェックします。someCondition が真の場合、next 関数を呼び出してテストを続行します。そうでない場合は、// ...別の処理を実行 で示されるように、別の処理を実行します。

next の注意点

  • next 関数は、Cypress バージョン 4.0 以降でのみ使用できます。
  • next 関数は、テストの進行を制御するために使用できますが、乱用するとテストコードが複雑になり、理解しにくくなる可能性があります。
  • next 関数は、非同期処理が完了するのを確実に待機してから次のコマンドに進みます。そのため、テストの実行速度に影響を与える可能性があります。

代替手段

next 関数の代わりに、以下の代替手段を使用することもできます。

  • Promise を使用する

非同期処理を扱う場合は、Promise を使用してコードをより明確に記述することができます。

  • コールバック関数を使用する

テストの進行を制御する場合は、コールバック関数を使用してコードを記述することができます。



非同期処理の完了を待つ

cy.request('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => {
    const postData = response.body;
    next(postData);
  })
  .then((postData) => {
    // ...postData を使用して処理を行う
  });

この例では、cy.request() コマンドは非同期的に API を呼び出し、その結果を postData 変数に格納します。その後、next 関数を呼び出して postData を次のコマンドに渡します。次のコマンドは、postData を使用して処理を行います。

テストの進行を制御する

cy.get('button').click()
  .then(() => {
    if (someCondition) {
      next();
    } else {
      // ...別の処理を実行
    }
  })
  .then(() => {
    // ...次の処理を行う
  });

この例では、cy.get('button').click() コマンドはボタンをクリックし、その結果を then() コールバックに渡します。その後、then() コールバック内で someCondition 変数をチェックします。someCondition が真の場合、next 関数を呼び出してテストを続行します。そうでない場合は、// ...別の処理を実行 で示されるように、別の処理を実行します。その後、次の then() コールバック内で次の処理を行います。

コマンドチェーンと next を組み合わせる

cy.get('input[type="text"]').type('Hello, world!')
  .next()
  .should('have.value', 'Hello, world!');

この例では、cy.get('input[type="text"]').type('Hello, world!') コマンドは要素を取得し、その要素に値を入力します。その後、next 関数を呼び出して次のコマンドに進む前に、現在のコマンドが完了するのを確実にします。最後に、should('have.value', 'Hello, world!') コマンドは、要素のアサーションを行います。

カスタムコマンドと next を使用する

Cypress.Commands.add('login', (username, password) => {
  cy.get('input[name="username"]').type(username);
  cy.get('input[name="password"]').type(password);
  cy.get('button[type="submit"]').click();
  next();
});

cy.login('user123', 'password123')
  .then(() => {
    // ...ログイン後の処理を行う
  });

この例では、Cypress.Commands.add() 関数を使用して login というカスタムコマンドを作成します。このコマンドは、ユーザー名とパスワードを受け取り、ログイン処理を実行します。その後、next 関数を呼び出して次のコマンドに進む前に、現在の処理が完了するのを確実にします。



Promise を使用する

非同期処理を扱う場合は、Promise を使用してコードをより明確に記述することができます。Promise は、非同期処理の結果を扱うための標準的な JavaScript API です。

cy.request('https://jsonplaceholder.typicode.com/posts/1')
  .then((response) => {
    const postData = response.body;
    return postData;
  })
  .then((postData) => {
    // ...postData を使用して処理を行う
  });

この例では、cy.request() コマンドは非同期的に API を呼び出し、その結果を Promise として返します。その後、then() メソッドを使用して Promise の結果を処理します。最初の then() メソッドは、postData 変数に API レスポンスの本文を格納します。2 番目の then() メソッドは、postData を使用して処理を行います。

コールバック関数を使用する

テストの進行を制御する場合は、コールバック関数を使用してコードを記述することができます。コールバック関数は、非同期処理が完了した後に呼び出される関数です。

cy.get('button').click((done) => {
  if (someCondition) {
    done();
  } else {
    // ...別の処理を実行
  }
});

// ...次の処理を行う

この例では、cy.get('button').click() コマンドはボタンをクリックし、コールバック関数 done を渡します。その後、then() コールバック内で someCondition 変数をチェックします。someCondition が真の場合、done() 関数を呼び出してテストを続行します。そうでない場合は、// ...別の処理を実行 で示されるように、別の処理を実行します。

非同期/待機コマンドを使用する

Cypress には、非同期処理を待機するための組み込みコマンドが用意されています。これらのコマンドを使用することで、next 関数を使用せずにコードを記述することができます。

  • cy.route(): スタブ API エンドポイントを作成します。
  • cy.fixture(): ローカルファイルからデータを非同期的に読み込みます。
  • cy.wait(): 特定の条件が満たされるまで待機します。
方法利点欠点
next簡潔で使いやすい乱用するとコードが複雑になる可能性がある
Promiseコードが明確で読みやすい多少冗長になる可能性がある
コールバック関数テストの進行を詳細に制御できるコードが煩雑になる可能性がある
非同期/待機コマンドコードが簡潔で読みやすいすべての状況で使用できるわけではない