Playwrightを用いたDjangoアプリのEnd-to-Endテスト

最終更新:2024年05月30日 公開:2024年05月28日

はじめに

Playwrightを用いて、以前作成した日記アプリに対してEnd-to-Endテストを実施します。日記アプリについてはこの記事の冒頭を参照してください。

End-to-Endテストの概要

End-to-Endテストはシステム全体に対して行われるテストです。

そのシステムの利用者の目線に立って、ブラウザ上で入力してから実行結果が返ってくるまでの一連の流れを検証します。

次の図は上から順にEnd-to-Endテスト、インテグレーションテスト、ユニットテストを表しています。

上に行くほど、テストの数は少なくなり、テストの対象範囲は広くなります。

Playwrightの紹介

PlaywrightはEnd-to-Endテストを行うソフトウェアの一種で、Microsoft社によって開発されています。

Playwrightには次の特徴があります。

  • 要素が実行可能になるまで自動的に待つことで、テストが不安定になることを防ぐ

同一の条件で同じテストを複数回実施した際に、失敗する場合と成功する場合があるという不安定な状態になってしまうことがあります。

このような状態のテストはFlaky testとよばれています。

Seleniumなどのソフトウェアを使用してテストを行う場合には、「システムの応答が完了するまで一定時間時間待つ」といったコードを開発者が書く必要があります。

しかし、何らかの原因でシステムの応答が完了するまでに通常より長く時間がかかってしまう場合があります。

この場合には、実行可能になる前に要素を参照しようとすることでエラーが起き、テストが失敗してしまうといったことがあります。

このように、システムの応答が完了するまでの待機時間を開発者側で指定することは、Flaky testの原因になりえます。

Playwrightでは、要素が実行可能になるまで自動的に待機することで、上に述べたような事態によってFlaky testが発生してしまうのを防ぐことができます。

 

Playwrightのインストール

こちらのページを参考にしてください 

私はnpmを使用してPlaywrightをインストールしました。事前にバージョン条件を満たしたnode.jsがインストールされている必要があることに注意して下さい。

インストール時に指定したテストフォルダにはexample.spec.tsファイル(Typescriptを選択した場合)が置いてあります。

このファイルにはhttps://playwright.dev/ (PlayWrightの公式ページ)に対する3つのテストが存在します。

 

ターミナル上でnpx playwright test

と入力すると、テストフォルダ内のテストコードがすべて実行されます。

テストが完了すると、次のようにテスト結果がブラウザ上に表示されます。

上の画面では「has title」という名前のテストが「chromium」、「firefox」、「webkit」の3つのブラウザで実行されて、成功したことを表しています。

PlaywrightによるE2Eテストの記述方法

Playwrightでは、~.spec.ts(Javascriptの場合はspec.js)という名前のファイルの中にテストを書いていきます。

ファイル内では、

import { test, expect } from '@playwright/test';
 
として、testモジュールとexportモジュールをインポートして、
 

test('テスト名', async ({ page }) => {
});
 
という関数の中にそれぞれのテストで行う操作を書いていきます。
 
テストを構成する重要な要素として、Locatorがあります。
 
Locatorを使うことで、操作したい要素を指定します。
 
Locatorを使用する際には次のように記述します。
 
await page.getByRole('button', { name: 'Sign in' }).click();
 
上のコードでは、page.getByRole()とよばれるLocatorを使用しています。
 
nameが’Sign in’である<button>要素を指定して、click()によってボタンをクリックしています。
 
Locatorに関する詳しい説明はこちらのページを参照してください。
 
 

VSCodeの拡張機能を使用する

PlayWrightの拡張機能である「Playwright Test for VSCode」を使用することで、容易にテストの生成やテスト範囲の制御を行うことができます。

テストの生成

拡張機能のインストールが完了したら、サイドバーでこちらのアイコン

をクリックして、拡張機能のメニュー画面を開いてください

メニュー画面の下部を見てください。

テストで使用するブラウザを、「PROJECTS」で選択できます。

「SETTING」では、テスト実行時のオプションが選択できます。

「Show browser」では、テスト実行時にブラウザが立ち上がって、テストの動作をリアルタイムで確認することができます。

「Show trace viewer」では、テスト終了時にブラウザ上で下の画像のような詳細な記録を見ることができます。

 

「Record new」ボタンを押すと、次のようにtest-1.spec.tsというファイルが生成されるとともに、ブラウザが開きます。

ブラウザでテストを行いたいページにアクセスして、テストしたい箇所の操作を行うことで、test-1.spec.ts上にテストコードが生成されていきます。

例えば、「トップページからログイン画面にアクセスして、ログイン画面でメールアドレスとパスワードを入力してログインする」という操作をテストしたい場合を考えます。この場合は、トップページからログイン画面にアクセスして、ブラウザ上でメールアドレスとパスワードを入力してログインボタンを押します。すると、次のようにテストコードが生成されています。

後は、実際にテストを実行してみて、過不足がある部分を手作業で修正することでテストコードが完成します。

「Playwright Test for VSCode」に関する詳しい説明はこちらのページを参考にしてください。

テスト範囲の制御

先ほどのように、

npx playwright test

を実行すると、テストフォルダ内のテストすべてが実行されます。「Playwright Test for VSCode」を使うことで、テストの実行範囲をファイル単位や個別のテスト単位で細かく制御することができます。

今回使用したテストは次のような構造になっています。例えば、login.spec.ts内にはloginとsignupという名前の

2つのテストが存在します。

各ファイルやテスト名の横の「Run Test」ボタン(▷のアイコン)を押すことでテストの実行ができます。

login.spec.tsの横の「Run Test」ボタンを押すとファイル内のlogin, signupテストが実行されます。

loginの横の「Run Test」ボタンを押すと、loginテストのみが実行されます。

日記アプリに対するEnd-to-Endテストの実行

Playwrightを用いて、実際に日記アプリに対してEnd-to-Endテストを実行します。

実施したそれぞれのテストに関して、テストの概要や使用したコード、スクリーンショットを記載しています。(page.screenshot()によって、現在テストを行っている画面のスクリーンショットが取得できます。)

アカウントの新規作成

まず、アカウントの新規作成ができるか確認します。

– 実装

test('signup', async ({ page }) => {
  await page.goto('http://localhost:8000/'); //トップページを開く
  await page.getByText('SIGN UP').click(); //サインアップページへ
  //メールアドレスとパスワードを入力して新規登録
  await page.getByPlaceholder('Email address').fill('new@gmail.com');
  await page.getByPlaceholder('Email address').press('Tab');
  await page.getByText('Password:').fill('00001111aa');
  await page.getByText('Password:').press('Tab');
  await page.getByText('Password (again):').fill('00001111aa');
  await page.screenshot({ path: 'signup_diary_1.png' });
  await page.getByRole('button', { name: '登録' }).click();
  await page.screenshot({ path: 'signup_diary_2.png' });
});
 
スクリーンショットを見ると、新規ユーザーの登録が行われていることを確認できます。
 
 

ログイン、ログアウト

先ほど作成したアカウントでログインをした後にログアウトします。

test('login_logout', async ({ page }) => {
  await page.goto('http://localhost:8000/'); //トップページを開く
  await page.getByRole('link', { name: 'LOG IN', exact: true }).click(); //ログインページへ
  //メールアドレスとパスワードを入力してログイン
  await page.getByPlaceholder('Email address').fill('new@gmail.com');
  await page.getByPlaceholder('Email address').press('Tab');
  await page.getByPlaceholder('Password').fill('00001111aa');
  await page.getByLabel('Remember Me:').check();
  await page.getByRole('button', { name: 'ログイン' }).click();
  await page.screenshot({ path: 'login_diary.png' });
  //ログアウト
  await page.getByText('LOG OUT').click();
  await page.screenshot({ path: 'logout_diary.png' });
});

日記の新規作成

Playwrightを用いて、新しい日記を作成します。

日記のタイトルを「Playwrightテスト」、本文を「Playwrightテスト本文」として、画像を1枚挿入しています。

//日記の新規作成のテスト
test('create', async ({ page }) => {
  await page.goto('http://localhost:8000/accounts/login/');
  await page.getByPlaceholder('Email address').click();
  await page.getByPlaceholder('Email address').fill('new@gmail.com');
  await page.getByPlaceholder('Email address').press('Tab');
  await page.getByPlaceholder('Password').fill('00001111aa');
  await page.getByRole('button', { name: 'ログイン' }).click();
  await page.getByRole('link', { name: '新規作成' }).click();
  await page.getByLabel('タイトル:').click();
  await page.getByLabel('タイトル:').fill('Playwrightテスト');
  await page.getByLabel('タイトル:').press('Tab');
  await page.getByLabel('本文:').fill('Playwrightテスト本文');
  await page.getByLabel('写真1:').click();
  await page.getByLabel('写真1:').setInputFiles('/home/yusuke/record/image-102.png'); //てきとうな写真をアップロード
  await page.screenshot({ path: 'create_diary_1.png' });
  await page.getByRole('button', { name: '作成' }).click();
  await page.screenshot({ path: 'create_diary_2.png' });
});

スクリーンショットを確認すると、無事に日記が作成されていることが確認できます。 

 

日記の編集

先ほど作成した日記の編集を行います。

新たに画像を1枚挿入して、ブログの本文を「Playwrightテスト本文更新」とします。


 
test('update', async ({ page }) => {
  await page.goto('http://localhost:8000/accounts/login/');
  await page.getByPlaceholder('Email address').click();
  await page.getByPlaceholder('Email address').fill('new@gmail.com');
  await page.getByPlaceholder('Email address').press('Tab');
  await page.getByPlaceholder('Password').fill('00001111aa');
  await page.getByLabel('Remember Me:').check();
  await page.getByRole('button', { name: 'ログイン' }).click();
 
  await page.getByRole('link', { name: 'Playwrightテスト Playwrightテスト本文' }).first().click();
  await page.getByRole('link', { name: '編集' }).click();
  await page.getByLabel('本文:').click();
  await page.getByLabel('本文:').fill('Playwrightテスト本文更新');
  await page.getByLabel('写真2:').click();
  await page.getByLabel('写真2:').setInputFiles('/home/yusuke/record/image-3.png');
  await page.screenshot({ path: 'update_diary_1.png' });
  await page.getByRole('button', { name: '更新' }).click();
  await page.screenshot({ path: 'update_diary_2.png' });
});
 
ブログが更新されていることを確認できます。

日記の削除

作成した日記を削除します。

test('delete', async ({ page }) => {
  await page.goto('http://localhost:8000/accounts/login/');
  await page.getByPlaceholder('Email address').click();
  await page.getByPlaceholder('Email address').fill('new@gmail.com');
  await page.getByPlaceholder('Password').click();
  await page.getByPlaceholder('Password').fill('00001111aa');
  await page.getByRole('button', { name: 'ログイン' }).click();
  await page.getByRole('link', { name: 'Playwrightテスト Playwrightテスト本文更新' }).click();
  await page.getByRole('link', { name: '削除' }).click();
  await page.screenshot({ path: 'delete_diary_1.png' });
  await page.getByRole('button', { name: '削除' }).click();
  await page.screenshot({ path: 'delete_diary_2.png' });
});
 
無事に削除が行われたことを確認できます。

 

まとめ

以上のように、Playwrightを使用することで以下の操作が正しく行われることを確認しました。

  • アカウントの作成
  • ログイン、ログアウト
  • ブログ記事の新規作成
  • ブログ記事の編集
  • ブログ記事の削除
アバター画像
フォーム作成クラウドサービス「Formzu(フォームズ)」を運営しているフォームズ株式会社です。
オフィスで働く方、ホームページを運営されている皆様へ
仕事の効率化、ビジネススキル、ITノウハウなど役立つ情報をお届けします。
  • 【初めての方へ】Formzuで仕事の効率化
  • 【初めての方へ】メールフォームについて