1708503380
2024-02-20 11:06:55
による モシェ・コロドニー
この投稿では、Web ベースのユーザー インターフェイス (UI) アプリケーションのエンドツーエンド (E2E) テストに新たな視点を提供する革新的なライブラリである SafeTest を紹介できることを嬉しく思います。
従来、UI テストは単体テストまたは統合テスト (エンドツーエンド (E2E) テストとも呼ばれます) のいずれかを通じて実施されてきました。 ただし、これらの方法にはそれぞれ固有のトレードオフがあり、テスト フィクスチャとセットアップを制御するか、テスト ドライバーを制御するかを選択する必要があります。
たとえば、使用するときは、 反応テストライブラリ、単体テスト ソリューションを使用すると、何をレンダリングするか、および基礎となるサービスとインポートがどのように動作するかを完全に制御できます。 ただし、実際のページを操作できなくなるため、次のようなさまざまな問題が発生する可能性があります。
- 次のような複雑な UI 要素を操作するのが難しい
コンポーネント。 - CORS セットアップまたは GraphQL 呼び出しをテストできない。
- ボタンのクリック能力に影響を与える Z インデックスの問題が可視化されていない。
- 複雑で直感的ではないテストの作成とデバッグ。
逆に、Cypress や Playwright などの統合テスト ツールを使用すると、ページを制御できますが、アプリのブートストラップ コードをインストルメント化する機能が犠牲になります。 これらのツールは、ブラウザをリモート制御して URL にアクセスし、ページと対話することによって動作します。 このアプローチには、次のような独自の課題があります。
- カスタム ネットワーク層 API 書き換えルールを実装せずに代替 API エンドポイントを呼び出すのは困難です。
- スパイ/モックに対してアサーションを行ったり、アプリ内でコードを実行したりすることができません。
- ダーク モードなどをテストするには、テーマ スイッチャーをクリックするか、上書きする localStorage メカニズムを知る必要があります。
- アプリのセグメントをテストできない。たとえば、ボタンをクリックして 60 秒タイマーのカウントダウンを待った後にのみコンポーネントが表示される場合、テストはそれらのアクションを実行する必要があり、テストには少なくとも 1 分かかります。
これらの課題を認識して、E2E コンポーネント テストのようなソリューションが登場し、次のようなソリューションが提供されています。 ヒノキ そして 劇作家。 これらのツールは、従来の統合テスト方法の欠点を修正しようとしていますが、そのアーキテクチャに起因する他の制限もあります。 ブートストラップ コードを使用して開発サーバーを起動し、必要なコンポーネントやセットアップ コードをロードします。これにより、OAuth や複雑なビルド パイプラインを備えた複雑なエンタープライズ アプリケーションを処理する能力が制限されます。 さらに、TypeScript の使用法を更新すると、Cypress/Playwright チームがランナーを更新するまでテストが中断される可能性があります。
SafeTest は、UI テストへの新しいアプローチでこれらの問題に対処することを目的としています。 主なアイデアは、 テストを実行するためのフックを挿入する、アプリケーションのブートストラップ段階のコードのスニペット (を参照してください。 セーフテストの仕組み これが何をしているのかについては、セクションを参照してください)。 SafeTest は遅延読み込みを利用してテストの実行時にのみテストを動的にロードするため、この動作がアプリの通常の使用に測定可能な影響を与えることはないことに注意してください (README の例では、テストは運用バンドルにまったく含まれていません)。 これが完了すると、Playwright を使用して定期テストを実行できるようになり、テストに必要な理想的なブラウザー コントロールを実現できます。
このアプローチにより、いくつかの興味深い機能も利用できるようになります。
- ノード テスト サーバーを実行する必要のない、特定のテストへのディープ リンク。
- ブラウザとテスト (ノード) コンテキスト間の双方向通信。
- Playwright に付属するすべての DX 機能へのアクセス (@playwright/test に付属のものを除く)。
- テストのビデオ録画、トレースの表示、さまざまなページ セレクター/アクションを試すためのページの一時停止機能。
- ブラウザ内のノードのスパイに対してアサーションを作成し、ブラウザ内の呼び出しのスナップショットと照合する機能。
SafeTest は、既存のソリューションの最良の部分を活用しているため、これまでに UI テストを実施したことのある人にとっては親しみやすいように設計されています。 アプリケーション全体をテストする方法の例を次に示します。
import { describe, it, expect } from 'safetest/jest';
import { render } from 'safetest/react';describe('my app', () => {
it('loads the main page', async () => {
const { page } = await render();
await expect(page.getByText('Welcome to the app')).toBeVisible();
expect(await page.screenshot()).toMatchImageSnapshot();
});
});
特定のコンポーネントも同様に簡単にテストできます
import { describe, it, expect, browserMock } from 'safetest/jest';
import { render } from 'safetest/react';describe('Header component', () => {
it('has a normal mode', async () => {
const { page } = await render( );
await expect(page.getByText('Admin')).not.toBeVisible();
});
it('has an admin mode', async () => {
const { page } = await render( );
await expect(page.getByText('Admin')).toBeVisible();
});
it('calls the logout handler when signing out', async () => {
const spy = browserMock.fn();
const { page } = await render( );
await page.getByText('logout').click();
expect(await spy).toHaveBeenCalledWith();
});
});
SafeTest は React Context を利用して、テスト中の値の上書きを可能にします。 これがどのように機能するかを示す例として、コンポーネントで fetchPeople 関数が使用されていると仮定します。
import { useAsync } from 'react-use';
import { fetchPerson } from './api/person';export const People: React.FC = () => {
const { data: people, loading, error } = useAsync(fetchPeople);
if (loading) return ;
if (error) return ;
return
;
}
オーバーライドを使用するように People コンポーネントを変更できます。
import { fetchPerson } from './api/person';
+import { createOverride } from 'safetest/react';+const FetchPerson = createOverride(fetchPerson);
export const People: React.FC = () => {
+ const fetchPeople = FetchPerson.useValue();
const { data: people, loading, error } = useAsync(fetchPeople);
if (loading) return ;
if (error) return ;
return
;
}
さて、テストでは、この呼び出しの応答をオーバーライドできます。
const pending = new Promise(r => { /* Do nothing */ });
const resolved = [{name: 'Foo', age: 23], {name: 'Bar', age: 32]}];
const error = new Error('Whoops');describe('People', () => {
it('has a loading state', async () => {
const { page } = await render(
() => pending}>
);
await expect(page.getByText('Loading')).toBeVisible();
});
it('has a loaded state', async () => {
const { page } = await render(
async () => resolved}>
);
await expect(page.getByText('User: Foo, name: 23')).toBeVisible();
});
it('has an error state', async () => {
const { page } = await render(
async () => { throw error }}>
);
await expect(page.getByText('Error getting users: "Whoops"')).toBeVisible();
});
});
render 関数は、初期アプリ コンポーネントに渡される関数も受け入れます。これにより、アプリ内の任意の場所に任意の要素を挿入できます。
it('has a people loaded state', async () => {
const { page } = await render(app =>
async () => resolved}>
{app}
);
await expect(page.getByText('User: Foo, name: 23')).toBeVisible();
});
オーバーライドを使用すると、API リクエストを組み合わせたサービス メソッドを保証するなど、複雑なテスト ケースを作成できます。 /foo、 /bar、 そして /baz、失敗した API リクエストのみに対する正しい再試行メカニズムがあり、戻り値を正しくマップします。 それで、もし /bar メソッドを解決するには 3 回の試行が必要で、合計 5 回の API 呼び出しが行われます。
オーバーライドは API 呼び出しだけに限定されません (使用することもできるため) page.route)、機能フラグや静的な値の変更など、特定のアプリ レベルの値をオーバーライドすることもできます。
+const UseFlags = createOverride(useFlags);
export const Admin = () => {
+ const useFlags = UseFlags.useValue();
const { isAdmin } = useFlags();
if (!isAdmin) return Permission error;
// ...
}+const Language = createOverride(navigator.language);
export const LanguageChanger = () => {
- const language = navigator.language;
+ const language = Language.useValue();
return
Current language is { language } ;
}describe('Admin', () => {
it('works with admin flag', async () => {
const { page } = await render(
{
const oldFlags = oldHook();
return { ...oldFlags, isAdmin: true };
}}>
);
await expect(page.getByText('Permission error')).not.toBeVisible();
});
});
describe('Language', () => {
it('displays', async () => {
const { page } = await render(
'abc'}>
);
await expect(page.getByText('Current language is abc')).toBeVisible();
});
});
オーバーライドは SafeTest の強力な機能であり、ここでの例は表面をなぞっただけです。 詳細と例については、を参照してください。 オーバーライドセクション で お読みください。
SafeTest は、ビデオ再生の自動リンク、Playwright トレース ビューアなどの強力なレポート機能をすぐに備えています。 マウントされたテスト済みコンポーネントへの直接のディープリンク。 SafeTest リポジトリ お読みください すべてへのリンク サンプルアプリ 同様に レポート
多くの大企業では、アプリを使用するために認証形式が必要です。 通常、localhost:3000 に移動すると、ページが永続的に読み込まれるだけになります。 localhost:8000 などの別のポートに移動する必要があります。このポートには、認証資格情報を確認したり、基礎となるサービス呼び出しに認証資格情報を挿入したりするためのプロキシ サーバーがあります。 この制限は、Cypress/Playwright コンポーネント テストが Netflix での使用に適さない主な理由の 1 つです。
ただし、通常は、アプリケーションへのログインや対話に使用できる資格情報を使用できるテスト ユーザーを生成できるサービスがあります。 これにより、SafeTest のライト ラッパーを作成し、そのテスト ユーザーを自動的に生成して想定することが容易になります。 たとえば、Netflix での基本的なやり方は次のとおりです。
import { setup } from 'safetest/setup';
import { createTestUser, addCookies } from 'netflix-test-helper';type Setup = Parameters[0] & {
extraUserOptions?: UserOptions;
};
export const setupNetflix = (options: Setup) => {
setup({
...options,
hooks: { beforeNavigate: [async page => addCookies(page)] },
});
beforeAll(async () => {
createTestUser(options.extraUserOptions)
});
};
これを設定した後、safetest/setup を使用する場所の代わりに上記のパッケージをインポートするだけです。
この投稿では SafeTest が React とどのように連携するかに焦点を当てましたが、それは React だけに限定されません。 SafeTest は Vue、Svelte、Angular でも動作し、NextJS や Gatsby 上でも実行できます。 また、スキャフォールディングで最初に使用したテスト ランナーに基づいて、Jest または Vitest のいずれかを使用して実行されます。 の サンプルフォルダー さまざまなツールの組み合わせで SafeTest を使用する方法を示しており、より多くのケースを追加するための貢献を奨励しています。
SafeTest の核心は、テスト ランナー、UI ライブラリ、ブラウザ ランナーのためのインテリジェントな接着剤です。 Netflix での最も一般的な使用方法は Jest/React/Playwright ですが、他のオプション用にアダプターを追加するのは簡単です。
SafeTest は、Netflix 内で採用されている強力なテスト フレームワークです。 これにより、テストを簡単に作成でき、障害が発生した時期と方法に関する包括的なレポートが提供され、再生ビデオを表示したり、テスト ステップを手動で実行して何が問題を確認したりするためのリンクが完備されています。 私たちは、これが UI テストにどのような革命をもたらすか楽しみにしています。また、皆様からのフィードバックや貢献を楽しみにしています。
#SafeTest #の紹介 #フロントエンド #テストへの新しいアプローチ #Netflix #テクノロジーブログ #年 #月