Cypress v6 から Fetch による API 呼び出しがデフォルトで監視できるようになっていた

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

E2E テストを実装する際、API のレスポンスを待ってから、何かの要素をチェックしたいケースがあります。Cypress では、Fetch による API の呼び出しがデフォルトで監視できず、別途オプションを付け足す必要があったのですが、Version 6 からはそれが不要になったようです。

Cypress - Changelog 6.0.0

テスト対象

例えば、従業員のデータを API から取得して、リストを作成するページがあったとします。API の呼び出しには Fetch を使用しています。

App.tsx

import React, { FC, useEffect, useState } from 'react';
import './App.css';

type Employee = {
  id: number;
  name: string;
  age: number;
};

const App: FC = () => {
  const [employees, setEmployees] = useState<Employee[]>([]);
  const listStyle = {
    marginBottom: '4px',
  };

  useEffect(() => {
    fetch('http://localhost:3001/employees')
      .then((response) => response.json())
      .then((data) => setEmployees(data));
  }, []);

  return (
    <div className="App">
      <h3>Employees</h3>
      <ul id="employees">
        {employees.map((employee) => (
          <li key={employee.id} style={listStyle}>
            {employee.name}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;

UI はこんな感じです。

API を呼び出した後、リストが作成されているか確認する E2E テストを実装するとします。

今まで(v4.9 ~ v5系)

失敗例

下記の実装で、/employeesのレスポンスを待ってから、リストが作成されているか確認できるのですが、

sample_spec.js

describe('サンプルテスト', () => {
  beforeEach(() => {
    cy.server();
    cy.route('GET', '/employees').as('employees');
  });

  it('Employees API のレスポンスを待つ', () => {
    cy.visit('/');
    cy.wait('@employees');
    cy.get('#employees > li').should('have.length', 5);
  });
});

このまま E2E テストを実行しても失敗に終わります。

$ cypress run
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped

====================================================================================================

  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:    5.6.0                                                                              │
  │ Browser:    Electron 85 (headless)                                                             │
  │ Specs:      1 found (sample_spec.js)                                                           │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────
                                                                                                    
  Running:  sample_spec.js                                                                  (1 of 1)


  サンプルテスト
    1) Employees API のレスポンスを待つ


  0 passing (6s)
  1 failing

  1) サンプルテスト
       Employees API のレスポンスを待つ:
     CypressError: Timed out retrying: `cy.wait()` timed out waiting `5000ms` for the 1st request to the route: `employees`. No request ever occurred.

https://on.cypress.io/wait
      at cypressErr (http://localhost:3000/__cypress/runner/cypress_runner.js:172713:18)
      at Object.errByPath (http://localhost:3000/__cypress/runner/cypress_runner.js:172764:10)
      at checkForXhr (http://localhost:3000/__cypress/runner/cypress_runner.js:159916:33)
      at http://localhost:3000/__cypress/runner/cypress_runner.js:159941:28
      at tryCatcher (http://localhost:3000/__cypress/runner/cypress_runner.js:10325:23)
      at Function.Promise.attempt.Promise.try (http://localhost:3000/__cypress/runner/cypress_runner.js:7599:29)
      at tryFn (http://localhost:3000/__cypress/runner/cypress_runner.js:165556:21)
      at whenStable (http://localhost:3000/__cypress/runner/cypress_runner.js:165594:12)
      at http://localhost:3000/__cypress/runner/cypress_runner.js:165089:16
      at tryCatcher (http://localhost:3000/__cypress/runner/cypress_runner.js:10325:23)
      at Promise._settlePromiseFromHandler (http://localhost:3000/__cypress/runner/cypress_runner.js:8260:31)
      at Promise._settlePromise (http://localhost:3000/__cypress/runner/cypress_runner.js:8317:18)
      at Promise._settlePromise0 (http://localhost:3000/__cypress/runner/cypress_runner.js:8362:10)
      at Promise._settlePromises (http://localhost:3000/__cypress/runner/cypress_runner.js:8442:18)
      at Promise._fulfill (http://localhost:3000/__cypress/runner/cypress_runner.js:8386:18)
      at http://localhost:3000/__cypress/runner/cypress_runner.js:10000:46
  From Your Spec Code:
      at Context.eval (http://localhost:3000/__cypress/tests?p=cypress/integration/sample_spec.js:106:8)




  (Results)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Tests:        1                                                                                │
  │ Passing:      0                                                                                │
  │ Failing:      1                                                                                │
  │ Pending:      0                                                                                │
  │ Skipped:      0                                                                                │
  │ Screenshots:  1                                                                                │
  │ Video:        true                                                                             │
  │ Duration:     5 seconds                                                                        │
  │ Spec Ran:     sample_spec.js                                                                   │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


  (Screenshots)

  -  /Users/hogehoge/fugafuga/piyopiyo/cypress/screenshots     (1280x720)
     /sample_spec.js/サンプルテスト -- Employees API のレスポンスを待つ (fai…               


  (Video)

  -  Started processing:  Compressing to 32 CRF                                                     
  -  Finished processing: /Users/hogehoge/fugafuga/piyopiyo/cypress    (0 seconds)
                          /videos/sample_spec.js.mp4                               


====================================================================================================

  (Run Finished)


       Spec                                              Tests  Passing  Failing  Pending  Skipped  
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✖  sample_spec.js                           00:05        1        -        1        -        - │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✖  1 of 1 failed (100%)                     00:05        1        -        1        -        -

cy.wait()した際に、API の呼び出しを監視できていなく、そのままタイムアウトしてしまうようです。

CypressError: Timed out retrying: `cy.wait()` timed out waiting `5000ms` for the 1st request to the route: `employees`. No request ever occurred.

成功例

Cypress では、v4.9 より Fetch による API の呼び出しを監視するためのオプションが追加されました。

For a long, long, loooong time, the Cypress network control could not "see" window.fetch calls and only understood XMLHttpRequest Ajax calls.
...
Meanwhile, we have added a quick fetch polyfill as an experimental feature in Cypress v4.9.0. By turning this feature on, the Cypress Test Runner will automatically replace window.fetch with a unfetch polyfill built on top of XMLHttpRequest object, making these Ajax requests "visible" to the Test Runner.

Cypress - experimental-fetch-polyfill

cypress.json に下記を追加するだけで OK です。

cypress.json

{
  ...
  "experimentalFetchPolyfill": true
}

これで E2E テストが成功するようになります。

$ cypress run
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped

====================================================================================================

  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:      5.6.0                                                                            │
  │ Browser:      Electron 85 (headless)                                                           │
  │ Specs:        1 found (sample_spec.js)                                                         │
  │ Experiments:  experimentalFetchPolyfill=true                                                   │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────
                                                                                                    
  Running:  sample_spec.js                                                                  (1 of 1)


  サンプルテスト
    ✓ Employees API のレスポンスを待つ (381ms)


  1 passing (409ms)


  (Results)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Tests:        1                                                                                │
  │ Passing:      1                                                                                │
  │ Failing:      0                                                                                │
  │ Pending:      0                                                                                │
  │ Skipped:      0                                                                                │
  │ Screenshots:  0                                                                                │
  │ Video:        true                                                                             │
  │ Duration:     0 seconds                                                                        │
  │ Spec Ran:     sample_spec.js                                                                   │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


  (Video)

  -  Started processing:  Compressing to 32 CRF                                                     
  -  Finished processing: /Users/hogehoge/fugafuga/piyopiyo    (0 seconds)                               


====================================================================================================

  (Run Finished)


       Spec                                              Tests  Passing  Failing  Pending  Skipped  
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  sample_spec.js                           404ms        1        1        -        -        - │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        404ms        1        1        -        -        -

v6 から

Version 6 からは intercept() を使用することで、Fetch による API 呼び出しを監視できるようになりました。cypress.jsonにオプションを付け足す必要はもうありません。

・ experimentalFetchPolyfill has been deprecated. We encourage you to use cy.intercept() to intercept requests using the Fetch API instead.

Cypress - Changelog 6.0.0

・ can intercept all types of network requests including Fetch API, page loads, XMLHttpRequests, resource loads, etc.

Cypress - intercept

実装は下記になります。

sample_spec.js

describe('サンプルテスト', () => {
  beforeEach(() => {
    cy.intercept({
      url: '/employees',
      method: 'GET',
    }).as('employees');
  });

  it('Employees API のレスポンスを待つ', () => {
    cy.visit('/');
    cy.wait('@employees');
    cy.get('#employees > li').should('have.length', 5);
  });
});

ちゃんと E2E テストが成功しました。

$ cypress run
Missing baseUrl in compilerOptions. tsconfig-paths will be skipped

====================================================================================================

  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:    6.0.1                                                                              │
  │ Browser:    Electron 87 (headless)                                                             │
  │ Specs:      1 found (sample_spec.js)                                                           │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


────────────────────────────────────────────────────────────────────────────────────────────────────
                                                                                                    
  Running:  sample_spec.js                                                                  (1 of 1)


  サンプルテスト
    ✓ Employees API のレスポンスを待つ (369ms)


  1 passing (404ms)


  (Results)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Tests:        1                                                                                │
  │ Passing:      1                                                                                │
  │ Failing:      0                                                                                │
  │ Pending:      0                                                                                │
  │ Skipped:      0                                                                                │
  │ Screenshots:  0                                                                                │
  │ Video:        true                                                                             │
  │ Duration:     0 seconds                                                                        │
  │ Spec Ran:     sample_spec.js                                                                   │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


  (Video)

  -  Started processing:  Compressing to 32 CRF                                                     
  -  Finished processing: /Users/hogehoge/fugafuga/piyopiyo    (0 seconds)                              


====================================================================================================

  (Run Finished)


       Spec                                              Tests  Passing  Failing  Pending  Skipped  
  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ ✔  sample_spec.js                           385ms        1        1        -        -        - │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
    ✔  All specs passed!                        385ms        1        1        -        -        -

おわりに

Cypress のちょいネタでした。Fetch による API 呼び出しを行う際の、ちょっとした躓きポイントが解消されましたね。

今回は以上になります。最後まで読んで頂きありがとうございました!