CypressでGoogleのOAuth認証のログインをする

CypressでGoogle, Facebook, Microsoft, GitHub などのOAuth認証のログインを実施する方法を紹介します。CypressでOAuth認証を実施する方法はいくつかあるのですが、この記事ではGoogleのOAuthに対してフォーム入力する方法を試しています。

CypressでOAuth認証の実施方法

多くのOAuthプロバイダーは、ロボット判定や、リクエスト制限などにより、CypressでOAuth認証が難しい場合があります。

Cypressの Best Practice を見ると、この問題を軽減する方法が以下のように4つほど記載されております。これらの方法は、OAuthプロバイダーや利用アプリでの実装方法によりうまくいかないケースもあるので状況に応じて使い分ける必要があります。

  • 方法1. cy.origin()OAuth認証のフォーム入力をする
  • 方法2. OAuthプロバイダーからのレスポンスをスタブ化してログインする
  • 方法3. cy.request()でOAuthプロバイダーの公開APIを直接リクエストしてログインする
  • 方法4. サーバーでテスト用のログインAPIを作成してOAuthプロバイダーを省略してログインする

このうち、この記事では方法1をご紹介します。

OAuth認証のサンプルアプリの用意

実際にCypressでOAuth認証を検証するためのサンプルアプリを用意します。Cypressで実際に動かして検証しない方は、この項目をスキップしても問題ありません。

今回は、Auth0のReactのサンプルアプリのauth0-samples/auth0-react-samplesを使います。Auth0のアカウント作成(無料)が必要になります。

Auth0のセットアップ

  • まず、Auth0の開発用のアカウント(無料)を作成します。
  • 次に、Auth0のダッシュボードから、「Applications」を選択し、「Default App」を選択します。
  • Default App 内の Settingsタブ内でいくつか操作します。
    • 「Domain」と「Client ID」の値をメモしておく
    • Application Type を「Single Page Application」に変更
    • Allowed Callback URLsAllowed Logout URLs, Allowd Web Origins に「http://localhost:3000」を追加

Auth0のReactサンプルアプリの設定

Auth0のReactサンプルアプリのauth0-samples/auth0-react-samplesをダウンロードします。

git clone https://github.com/auth0-samples/auth0-react-samples.git

# サンプルアプリのルートディレクトリに移動
cd auth0-react-samples/Sample-01

次に、Auth0の設定情報を記載する設定ファイルを作成します。

touch src/auth_config.json

そして、先ほどAuth0のSettingsタブでコピーした「Domain」と「Client ID」をsrc/auth_config.jsonに記述します。

{
  "domain": "<Your Auth0 Domain>",
  "clientId": "<Your Auth0 Client ID>"
}

最後に、サンプルアプリを起動します。

npm install
npm run dev

サンプルのアプリが表示されることが確認できればOKです。

方法1. cy.origin()OAuth認証のフォーム入力をする

CypressからOAuth認証のフォーム入力を実施してOAuth認証を実現します。通常のCypressだとうまくいかないので、cy.originを使うことでAuth0やGoogleページにアクセスします。

ここからは、Cypressがインストールされているリポジトリで操作をします。Cypressのインストール方法や簡単な使い方を知りたい場合はCypressをさわってみるを参照ください。

# Cypressがインストールされているリポジトリに移動
cd your-installed-cypress-project

妨害するサードパーティコードの変更を有効にする

まずcypress.config.js内でexperimentalModifyObstructiveThirdPartyCodetrueにします。若干力技ですが、Cypressの操作を妨害するサードパーティのコードをCypressが変更するようになります。

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  // 妨害するサードパーティコードの変更を有効にする
  // Doc: https://docs.cypress.io/guides/guides/web-security#Modifying-Obstructive-Third-Party-Code
  experimentalModifyObstructiveThirdPartyCode: true,
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
  },
});

cypress.env.jsonの設定

cypress.env.jsonファイルにAuth0のドメインGoogleのログイン情報を設定します。もしファイルがない場合を追加してください。cypress.env.json環境変数は、Cypress.envで取得できるようになります。

{
  "BASE_URL": "http://localhost:3000",
  "AUTH0_DOMAIN": "<Your Auth0 Domain>",
  "GOOGLE_USERNAME": "<Your Google Account Username>",
  "GOOGLE_PASSWORD": "<Your Google Account Password>",
}

Googleログイン用の関数を追加

cypress/support/oauth-login.jsのファイルを作成して、Googleログイン用の関数を追加します。cy.originを使うことでAuth0やGoogleの別ドメインのページにアクセスできるようにしています。

// Googleログイン用の関数
function loginToGoogle(username, password) {
  Cypress.on(
    'uncaught:exception',
    (err) =>
      !err.message.includes('ResizeObserver loop') &&
      !err.message.includes('Error in protected function')
  );

  cy.visit(Cypress.env('BASE_URL'));

  cy.get('#qsLoginBtn').click();

  // Auth0のログインページでGoogleボタンをクリック
  // doc: https://docs.cypress.io/api/commands/origin
  cy.origin(Cypress.env('AUTH0_DOMAIN'), () => {
    cy.scrollTo('bottom');
    cy.get('form[data-provider="google"]').submit();
  });

  // Googleのログインページでユーザー名とパスワードを入力
  // doc: https://docs.cypress.io/api/commands/origin
  cy.origin(
    'https://accounts.google.com',
    {
      args: {
        username,
        password,
      },
    },
    ({ username, password }) => {
      Cypress.on(
        'uncaught:exception',
        (err) =>
          !err.message.includes('ResizeObserver loop') &&
          !err.message.includes('Error in protected function')
      );

      cy.get('input[type="email"]').type(username, { log: false });
      // NOTE: 要素はフォームに存在するが、非表示になって再レンダリングされるためwait()を追加
      cy.contains('次へ').click().wait(4000);

      cy.get('[type="password"]').type(password, { log: false });
      cy.contains('次へ').click().wait(4000);
    }
  );

  // ログイン時に表示されるプロフィールドロップダウンが存在することを確認
  cy.get('#profileDropDown').should('exist');
}

また、今後OAuth認証のプロバイダーを増やしやすくするために、loginViaOAuthという関数を追加しておきます。

// OAuth経由でログインする関数
export function loginViaOAuth(provider) {
  switch (provider) {
    case 'google':
      loginToGoogle(
        // cypress.env.json に設定したGoogleのログイン情報を使う
        Cypress.env('GOOGLE_USERNAME'),
        Cypress.env('GOOGLE_PASSWORD')
      );
      break;
      // NOTE: ここに他のプロバイダーの認証を追加できる
    default:
      throw new Error('no provider configured!');
  }
}

CypressのOAuth認証コマンドを作成する

作成したOAuth認証の関数をCypressから呼び出せるようにするためにCypress.Commands.addloginViaOAuthという名前で登録します。

import { loginViaOAuth } from './oauth-login';

Cypress.Commands.add('loginViaOAuth', (provider) => {
  cy.log(`loginViaOAuth : ${provider}`);
  loginViaOAuth(provider)
});

Cypressのテスト内でOAuth認証コマンドを呼び出す

では、cypress/e2e/oauth-login.cy.jsというCypressのテストファイルを作成します。そして、cy.loginViaOAuthを呼び出して、OAuth認証を実行します。

describe('Google Login Demo', () => {
  beforeEach(() => {
    // GoogleのOAuth認証を実施
    cy.loginViaOAuth('google')
  })

  it('shows logged in user profile page', () => {
    cy.visit('http://localhost:3000/profile')
    cy.location('pathname').should('eq', '/profile')
    cy.contains(Cypress.env('GOOGLE_USERNAME')).should('exist')
  })
})

GoogleOAuth認証のテストが書けたので、npx cypress openでテストを実行してみます。

cy.sessionでログイン情報を保持する

現状だと、テストのたびに毎回ログインフローを実行してしまい、テスト実行時間がとても長くなってしまいます。そのため、cy.sessionを使ってログイン情報をブラウザコンテキスト間でキャッシュするようにします。

import { loginViaOAuth } from './oauth-login';

Cypress.Commands.add('loginViaOAuth', (provider) => {
  cy.log(`loginViaOAuth : ${provider}`);

  // cy.sessionを使ってログイン情報をキャッシュ
  // Doc: https://docs.cypress.io/api/commands/session
  cy.session(`oauth-${provider}`,
    () => loginViaOAuth(provider),
    {
      // ログイン時のプロフィールドロップダウンが表示されていない場合
      // loginViaOAuth(provider) を実行する
      validate: () => {
        cy.visit(Cypress.env('BASE_URL'))
        cy.get('#profileDropDown').should('exist')
      },
    }
  );
});

Cypressのブラウザ画面をリロードすると、キャッシュが再利用されてGoogleのログインフローが省略されることが確認できます。

関連する情報