コンテンツにスキップ

Node.js Playwright E2E テスト

ブラウザーテストは別プロセスの HTTP サーバーへアクセスします。テストプロセスの DATABASE_URL を変更しても、サーバーが保持する DB プールの接続先は変わりません。Playwright のワーカーごとに pgmem サーバー、フォーク、アプリプロセスを持たせます。アプリの起動時にフォーク URL を渡し、各ケースの前に同じフォークをリセットします。アプリと DB 接続はワーカーの間ずっと維持されます。

ワーカー単位のアプリとケース単位のリセット

Section titled “ワーカー単位のアプリとケース単位のリセット”

以下の fixture は、pgmem とアプリをワーカーごとに一度起動します。prepare はフォーク前にプロジェクトのマイグレーションと基準シードを適用します。自動 fixture の resetDb は、ブラウザーテストの前に毎回その基準状態へ戻します。appUrl はワーカー単位なので、同じワーカーの page テストは同じアプリプロセスにアクセスします。

test/fixtures.js
import { test as base, expect } from '@playwright/test';
import { PgmemServer, startTestApp } from '@pgmem/core';
import { migrateAndSeed } from './prepare-db.js';
export const test = base.extend({
pgmem: [async ({}, use) => {
const server = await PgmemServer.start({
database: 'app',
prepare: ({ url }) => migrateAndSeed(url),
});
try {
const fork = await server.fork();
try { await use(fork); }
finally { await fork.close(); }
} finally {
await server.close();
}
}, { scope: 'worker' }],
appUrl: [async ({ pgmem }, use) => {
const app = await startTestApp({
fork: pgmem,
args: ['./src/server.js'],
healthPath: '/health',
});
try {
await use(app.url);
} finally { await app.close(); }
}, { scope: 'worker' }],
resetDb: [async ({ pgmem, appUrl }, use) => {
await pgmem.reset();
await use();
}, { auto: true }],
});
export { expect };

startTestApp は利用可能なループバックポートを探し、フォークの DATABASE_URL と PORT を子プロセスに渡し、GET /health を待ってからアプリを渡します。終了処理も行います。command、portEnv、databaseEnv、healthPath、起動時の timeoutMs は変更できます。src/server.js は PORT で待ち受け、プール作成時に DATABASE_URL を使い、準備完了後に GET /health に応答する必要があります。migrateAndSeed(url) は prepare から戻る前に、開いた DB 接続をすべて閉じてください。

test/orders.spec.js
import { test, expect } from './fixtures.js';
test('注文を作成する', async ({ page, appUrl }) => {
await page.goto(`${appUrl}/orders`);
await page.getByRole('button', { name: 'New order' }).click();
await expect(page.getByText('Order created')).toBeVisible();
});
test('次のケースは準備済みデータから始まる', async ({ page, appUrl }) => {
await page.goto(`${appUrl}/orders`);
await expect(page.getByText('No orders')).toBeVisible();
});

別のデータセットを使う場合は、ワーカーのフォークにシードを入れて fork.snapshot() を呼び、fork.reset({ snapshot: seeded }) で戻します。ワーカー終了までスナップショットを保持してください。同一プロセス内の HTTP テストは API テスト を参照してください。

Playwright の workers: 4 などでワーカー数を制限し、ワーカー単位の pgmem・fork・appUrl fixture を一緒に維持します。各ワーカーが異なるポートのアプリプロセスを起動し、自分のフォークだけを復元します。Playwright の並列モードで同じファイルのケースが別ワーカープロセスに分かれても、各ワーカーが独自の fixture を作れば分離されます。全ワーカーで外部アプリ URL や DB フォークを共有すると、この分離は失われます。同じワーカーでは次の reset 前に HTTP リクエスト、バックグラウンドジョブ、トランザクションを完了させます。pgmem とアプリのメモリ使用量に合わせてワーカー数を決めてください。