はじめに
社内システムを開発していて、パフォーマンステストを実施することになりました。
以前フロントエンド開発の単体テストにPlaywrightを使用していた経験もあり、ArtilleryとPlaywrightの組み合わせでパフォーマンステストを実施することにしました。
この記事では、Artillery (Playwright engine)でパフォーマンステストを実施する際の、スクリプト作成時の実践Tipsを共有します。
Artillery (Playwright engine)とは
負荷テストツールであるArtilleryから、ブラウザテストツールであるPlaywrightを実行して、負荷テスト(パフォーマンステスト)を行う仕組みです。
SPAなどのWebシステムに対して、単純なAPI負荷だけでなく、ブラウザでの画面遷移・入力・クリックなど、実際のユーザー操作に近い形で負荷を再現できます。
実行環境
バージョン情報
使用した各パッケージ等のバージョンは以下の通りです。
PS > npx artillery --version
___ __ _ ____
_____/ | _____/ /_(_) / /__ _______ __ ___
/____/ /| | / ___/ __/ / / / _ \/ ___/ / / /____/
/____/ ___ |/ / / /_/ / / / __/ / / /_/ /____/
/_/ |_/_/ \__/_/_/_/\___/_/ \__ /
/____/
VERSION INFO:
Artillery: 2.0.23
Node.js: v22.14.0
OS: win32
PS > npx playwright --version
Version 1.52.0
Artillery公式サイト - Playwright engineに「Playwright engineはArtilleryに組み込まれているので、個別にインストールは不要です」と記載されているため、個別にPlaywrightをインストールする必要はありません。
もし個別にインストールする場合は、当然ではありますが、Artillery公式サイト - Playwright compatibilityに記載されているArtillery versionとPlaywright versionの組み合わせでインストールしましょう。著者は初め両方の最新バージョンをインストールして実行時エラーになりました。
実行方法
ローカルPCで実行しました。(今回は比較的小規模なパフォーマンステストだったことや、クラウド上で実行する場合のセキュリティリスク、コスト等の観点から)
同時実行ユーザー数200で実施したかったため、1端末あたり30または25ユーザーで、7端末(チームメンバー7人)で一斉にテストスクリプト実行する方法を取りました。
ローカルPCで実行する場合は、CPUやメモリがボトルネックになります。
RAM16GBのローカルPCでメモリ使用率40%程度の状態から、ユーザー数30で実行した場合、最大メモリ使用率が80%程度になりました。(当然スクリプトの内容やテスト対象システムによって変わります。安定稼働させるためにはメモリ使用率80%程度に収まるようにするのが良さそうでした)
大規模なパフォーマンステストを実施する場合は、Artilleryがサポートしているクラウド上での実行などの方法を検討してください。
テスト対象システム
検証環境や本番環境にデプロイ済みの社内Webシステムに対して負荷をかけました。
社内システムや自社システムであっても、実施内容や実施時間帯等を関係者合意のうえ、システムダウンに備えた監視体制等を整えて実施しましょう。
他社が運営・管理しているシステム等に許可なく負荷をかけてはいけません。
テストスクリプト作成方法
Artillery公式サイト - Create your test definitionにテストスクリプト作成方法が以下の2種類記載されています。
- すべてTypeScriptで記述する方法
- Artillery設定のYAMLと、PlaywrightコードのTypeScript/JSを組み合わせる方法
今回は前者を採用しています。(1ファイルにまとまっている方がすっきりすると考えました)
テストスクリプト(Artillery設定部分)
テストスクリプトの最初、Artillery設定部分です。
各設定値の意味はソース内コメントを参照してください。
// タイムアウト時間(パフォーマンス低下により遅延する可能性があるため長めに設定(デフォルトは30秒)。
// TimeoutErrorでvuserが終了した場合、maxVusersを下回るため、その分新規vuserが起動される)
const timeout = 90000;
// Artillery設定
export const config = {
target: "dummy", // 本来ベースURLを設定するが、今回は複数URLにアクセスするためダミー設定(必須項目)
engines: {
playwright: {
launchOptions: {
headless: true, // GUI(ブラウザ)表示なし。falseにするとブラウザが起動して動作を目視確認できる
slowMo: 1000, // 各操作に1秒遅延を入れる(実際のユーザー操作に近い操作間隔で負荷再現)
},
defaultNavigationTimeout: timeout, // タイムアウト時間
showAllPageMetrics: true, // すべてのページのメトリックスを出力(デフォルトはtargetに設定されたURLのみ)
extendedMetrics: true, // 拡張メトリックスを出力
},
},
phases: [
{
duration: 300, // テスト実行時間(秒) この時間の間、新規ユーザーが起動する。
arrivalRate: 1, // 1秒ごとの追加ユーザー数
maxVusers: 30, // 最大同時ユーザー数制限
},
],
};
// パフォーマンステスト処理本体の実行前に一度だけ実行される関数名設定
export const before = {
engine: "playwright",
testFunction: beforePerformanceTest,
};
// パフォーマンステスト処理本体の関数名設定
export const scenarios = [
{
engine: "playwright",
testFunction: performanceTest,
},
];
設定は一例です。他にも多くの設定があるため、詳細は以下を参照してください。
Artillery公式サイト - Test Scripts
Artillery公式サイト - Playwright / browser configuration options
テストスクリプト(Playwrightコード部分・最小限)
テストスクリプトのArtillery設定部分より後の部分です。
スクリプト実行が可能な最小限のコードです。
import { Page } from "playwright";
// パフォーマンステスト処理本体の前に一度だけ実行される関数
async function beforePerformanceTest(page: Page, context: Context) {
// 前処理があれば
}
// パフォーマンステスト処理本体の関数
async function performanceTest(page: Page, context: Context) {
// 画面アクセス
await page.goto("https://~~~"); // テスト対象システムのURL
// 文字入力と保存ボタン押下を20回繰り返す
for (let i = 1; i <= 20; i++) {
await page.fill("#INPUT_FIELD", "inputText");
await page.click(`text=save`);
}
}
この記事ではPlaywrightコードの基本的な書き方は紹介していません。Playwright公式サイト - Writing testsを参照してください。
テストスクリプト実行コマンド
以下のコマンドで実行します。
ブラウザが起動し(headless: true の場合はGUI表示なし)、スクリプトに記載された操作が実行されます。
PS > npx artillery run .\tests\performanceTest\performanceTest.test.ts
以下のエラーが発生する場合は、package.jsonの "type": "module", を "type": "commonjs", に変更を試してみてください。変更により他の影響が発生する可能性もあるので、十分に注意してください。
module.exports = __toCommonJS(performanceTest_test_exports);
^
ReferenceError: module is not defined
コマンドを実行してから、すぐ起動する時となぜか数分経ってから起動する時がありました。(2回目以降は早く起動することが多かったです)
メトリクスレポート
テストスクリプトを実行すると、実行中10秒ごとのメトリクスと終了時サマリーのメトリクスがコンソールに出力されます。
以下はメトリクスレポートの出力例です。
maxVusers: 10 の例です。
スクリプトの内容は前述のコードとは異なります。
実際のメトリクスレポートを元に、例として掲載用に一部内容を変更しています。
メトリクスレポート出力例(展開してご覧ください)
--------------------------------------
Metrics for period to: 15:42:20(+0900) (width: 1.349s)
--------------------------------------
browser.http_requests: ......................................................... 12
browser.memory_used_mb:
min: ......................................................................... 1.5
max: ......................................................................... 1.7
mean: ........................................................................ 1.6
median: ...................................................................... 1.5
p95: ......................................................................... 1.5
p99: ......................................................................... 1.5
browser.page.FCP.https://~~~:
min: ......................................................................... 192
max: ......................................................................... 376
mean: ........................................................................ 284
median: ...................................................................... 190.6
p95: ......................................................................... 190.6
p99: ......................................................................... 190.6
browser.page.TTFB.https://~~~:
min: ......................................................................... 139.3
max: ......................................................................... 280.2
mean: ........................................................................ 209.8
median: ...................................................................... 138.4
p95: ......................................................................... 138.4
p99: ......................................................................... 138.4
browser.page.codes.200: ........................................................ 12
browser.page.domcontentloaded: ................................................. 2
browser.page.domcontentloaded.https://~~~: .................................. 2
browser.page.dominteractive:
min: ......................................................................... 178
max: ......................................................................... 367
mean: ........................................................................ 272.5
median: ...................................................................... 179.5
p95: ......................................................................... 179.5
p99: ......................................................................... 179.5
browser.page.dominteractive.https://~~~:
min: ......................................................................... 178
max: ......................................................................... 367
mean: ........................................................................ 272.5
median: ...................................................................... 179.5
p95: ......................................................................... 179.5
p99: ......................................................................... 179.5
vusers.created: ................................................................ 2
vusers.created_by_name.0: ...................................................... 2
--------------------------------------
Metrics for period to: 15:42:30(+0900) (width: 9.275s)
--------------------------------------
browser.http_requests: ......................................................... 48
browser.memory_used_mb:
min: ......................................................................... 1.5
max: ......................................................................... 1.8
mean: ........................................................................ 1.6
median: ...................................................................... 1.7
p95: ......................................................................... 1.7
p99: ......................................................................... 1.7
browser.page.FCP.https://~~~:
min: ......................................................................... 172
max: ......................................................................... 272
mean: ........................................................................ 201.5
median: ...................................................................... 190.6
p95: ......................................................................... 210.6
p99: ......................................................................... 210.6
browser.page.TTFB.https://~~~:
min: ......................................................................... 124.8
max: ......................................................................... 160.8
mean: ........................................................................ 140.8
median: ...................................................................... 138.4
p95: ......................................................................... 147
p99: ......................................................................... 147
browser.page.codes.200: ........................................................ 48
browser.page.domcontentloaded: ................................................. 8
browser.page.domcontentloaded.https://~~~: .................................. 8
browser.page.dominteractive:
min: ......................................................................... 160
max: ......................................................................... 252
mean: ........................................................................ 186.4
median: ...................................................................... 175.9
p95: ......................................................................... 194.4
p99: ......................................................................... 194.4
browser.page.dominteractive.https://~~~:
min: ......................................................................... 160
max: ......................................................................... 252
mean: ........................................................................ 186.4
median: ...................................................................... 175.9
p95: ......................................................................... 194.4
p99: ......................................................................... 194.4
vusers.created: ................................................................ 8
vusers.created_by_name.0: ...................................................... 8
vusers.skipped: ................................................................ 2
〜〜〜中略〜〜〜
All VUs finished. Total time: 2 minutes, 32 seconds
--------------------------------
Summary report @ 15:44:50(+0900)
--------------------------------
browser.http_requests: ......................................................... 3126
browser.memory_used_mb:
min: ......................................................................... 1.5
max: ......................................................................... 20.8
mean: ........................................................................ 7
median: ...................................................................... 3.4
p95: ......................................................................... 20.7
p99: ......................................................................... 20.7
browser.page.CLS.https://~~~:
min: ......................................................................... 0
max: ......................................................................... 0
mean: ........................................................................ 0
median: ...................................................................... 0
p95: ......................................................................... 0
p99: ......................................................................... 0
browser.page.CLS.https://===:
min: ......................................................................... 0
max: ......................................................................... 0
mean: ........................................................................ 0
median: ...................................................................... 0
p95: ......................................................................... 0
p99: ......................................................................... 0
browser.page.FCP.https://~~~:
min: ......................................................................... 232
max: ......................................................................... 332
mean: ........................................................................ 264.8
median: ...................................................................... 257.3
p95: ......................................................................... 295.9
p99: ......................................................................... 295.9
browser.page.FCP.https://===:
min: ......................................................................... 172
max: ......................................................................... 1388
mean: ........................................................................ 672.8
median: ...................................................................... 376.2
p95: ......................................................................... 1326.4
p99: ......................................................................... 1326.4
browser.page.FID.https://~~~:
min: ......................................................................... 1.1
max: ......................................................................... 1.8
mean: ........................................................................ 1.5
median: ...................................................................... 1.4
p95: ......................................................................... 1.7
p99: ......................................................................... 1.7
browser.page.FID.https://===:
min: ......................................................................... 0.5
max: ......................................................................... 3
mean: ........................................................................ 1.1
median: ...................................................................... 0.7
p95: ......................................................................... 2.1
p99: ......................................................................... 2.1
browser.page.INP.https://~~~:
min: ......................................................................... 24
max: ......................................................................... 96
mean: ........................................................................ 34.4
median: ...................................................................... 23.8
p95: ......................................................................... 32.1
p99: ......................................................................... 32.1
browser.page.INP.https://===:
min: ......................................................................... 24
max: ......................................................................... 40
mean: ........................................................................ 29.6
median: ...................................................................... 23.8
p95: ......................................................................... 40
p99: ......................................................................... 40
browser.page.LCP.https://~~~:
min: ......................................................................... 1284
max: ......................................................................... 1752
mean: ........................................................................ 1511.6
median: ...................................................................... 1583.6
p95: ......................................................................... 1676.2
p99: ......................................................................... 1676.2
browser.page.LCP.https://===:
min: ......................................................................... 372
max: ......................................................................... 1288
mean: ........................................................................ 878.8
median: ...................................................................... 576.2
p95: ......................................................................... 1118.1
p99: ......................................................................... 1118.1
browser.page.TTFB.https://~~~:
min: ......................................................................... 118.5
max: ......................................................................... 122.1
mean: ........................................................................ 120.7
median: ...................................................................... 120.3
p95: ......................................................................... 120.3
p99: ......................................................................... 120.3
browser.page.TTFB.https://===:
min: ......................................................................... 124.8
max: ......................................................................... 378.2
mean: ........................................................................ 235.3
median: ...................................................................... 267.8
p95: ......................................................................... 376.2
p99: ......................................................................... 376.2
browser.page.codes.200: ........................................................ 3086
browser.page.codes.201: ........................................................ 20
browser.page.codes.302: ........................................................ 20
browser.page.domcontentloaded: ................................................. 40
browser.page.domcontentloaded.https://~~~: .................................. 20
browser.page.domcontentloaded.https://===: .................................. 20
browser.page.dominteractive:
min: ......................................................................... 15
max: ......................................................................... 1009
mean: ........................................................................ 248.1
median: ...................................................................... 175.9
p95: ......................................................................... 699.4
p99: ......................................................................... 727.9
browser.page.dominteractive.https://~~~:
min: ......................................................................... 15
max: ......................................................................... 25
mean: ........................................................................ 19.7
median: ...................................................................... 19.1
p95: ......................................................................... 23.8
p99: ......................................................................... 23.8
browser.page.dominteractive.https://===:
min: ......................................................................... 160
max: ......................................................................... 1009
mean: ........................................................................ 406.1
median: ...................................................................... 368.8
p95: ......................................................................... 727.9
p99: ......................................................................... 727.9
vusers.completed: .............................................................. 10
vusers.created: ................................................................ 10
vusers.created_by_name.0: ...................................................... 10
vusers.failed: ................................................................. 0
vusers.session_length:
min: ......................................................................... 142272.3
max: ......................................................................... 143550.7
mean: ........................................................................ 142861.9
median: ...................................................................... 142963.7
p95: ......................................................................... 142963.7
p99: ......................................................................... 142963.7
vusers.skipped: ................................................................ 10
各メトリクスの説明は以下を参照してください。
Artillery公式サイト - Metrics reported by Artillery
Artillery公式サイト - Metrics reported by the engine
Tips
最小限版では味気ないので、今回パフォーマンステスト実施するにあたり、工夫したポイントを紹介していきます。
実行時に変数に値を設定
Contextインタフェースを定義することで、実行時に動的に値を設定して使用することができます。
また、$uuidを使用して実行ユーザーごとのvirtual user IDを取得することができます。
// 実行モード設定
let executeMode = 1; // この部分だけ実行前に手動で書き換える
// インタフェース定義
interface Context {
vars: {
targetURL: string; // targetURL
$uuid: string; // virtual user ID。Artilleryが自動で設定($が付くことに注意)
};
}
// パフォーマンステスト処理本体の前に一度だけ実行される関数
async function beforePerformanceTest(page: Page, context: Context) {
// 環境情報設定
if (executeMode === 1) {
// 検証環境
context.vars.targetURL = "https://~~~";
} else if (executeMode === 9) {
// 本番環境
context.vars.targetURL = "https://△△△";
}
}
設定情報出力
コンソール(と後述のファイル)に設定情報を出力しておくことで、テスト実施時の設定を記録に残せます。
// パフォーマンステスト処理本体の前に一度だけ実行される関数
async function beforePerformanceTest(page: Page, context: Context) {
console.log("##### configuration output START #####");
console.log(`config: ${JSON.stringify(config, null, 2)}`);
console.log(`executeMode: ${executeMode}`);
console.log(`targetURL: ${context.vars.targetURL}`);
console.log(`timeout: ${timeout}`);
console.log("##### configuration output END #####");
}
##### configuration output START #####
config: {
"target": "dummy",
"engines": {
"playwright": {
"launchOptions": {
"headless": true,
"slowMo": 1000
},
"defaultNavigationTimeout": 90000,
"showAllPageMetrics": true,
"extendedMetrics": true
}
},
"phases": [
{
"duration": 300,
"arrivalRate": 1,
"maxVusers": 30
}
]
}
executeMode: 1
targetURL: https://~~~
timeout: 90000
##### configuration output END #####
コンソール出力をファイルにも出力
コンソール出力される前述の設定情報とArtilleryメトリクスレポートを、ファイルにも出力して記録に残せます。
異なる設定で複数回実行し、結果を比較する際に分かりやすくて便利です。
PS > npx artillery run .\tests\performanceTest\performanceTest.test.ts 2>&1 | Tee-Object -FilePath ".\tests\performanceTest\PerformanceTest_Report_$(Get-Date -Format 'yyyyMMddHHmmss').txt"
レポートをJSONファイルに出力する方法や、Artillery Cloudを利用して視覚的で高度なレポートを使用することもできます。(今回はコンソール出力されるメトリクスレポートで十分でしたので使用していません)
詳細は以下を参照してください。
Artillery公式サイト - --output - create a JSON report
Artillery公式サイト - Set up cloud reporting
操作ログ出力
操作ログをファイル出力して、実行ユーザーごとの各操作が行われた時刻を記録できます。
通常時と比較した処理遅延発生状況の分析や、エラー発生時のエラー発生タイミングの特定などに利用できます。
import fs from "fs";
function outputOperationLog(vuserId: string, process: string) {
const nowString = new Date().toISOString(); // UTC
const logMessage = `[LOG] Virtual User ID: ${vuserId}, Timestamp: ${nowString}, Process: ${process}\n`;
// ファイル出力(ファイル出力失敗時はコンソール出力)
fs.appendFile(operationLogFilePath, logMessage, (err) => {
if (err) {
console.error(
"Failed to write operation log:",
err,
"\nFailed to write the following operation log:",
logMessage,
);
}
});
}
outputOperationLog(context.vars.$uuid, "Process Start");
[LOG] Virtual User ID: 7cf53acc-7092-4ec7-a897-23e6e5a7b5d2, Timestamp: 2026-02-02T09:21:48.257Z, Process: Process Start
[LOG] Virtual User ID: 65f87b12-f812-4570-a936-b1195e33d2d4, Timestamp: 2026-02-02T09:21:48.785Z, Process: Process Start
[LOG] Virtual User ID: 7cf53acc-7092-4ec7-a897-23e6e5a7b5d2, Timestamp: 2026-02-02T09:22:30.649Z, Process: Access to Login page
[LOG] Virtual User ID: 65f87b12-f812-4570-a936-b1195e33d2d4, Timestamp: 2026-02-02T09:22:31.129Z, Process: Access to Login page
[LOG] Virtual User ID: 7cf53acc-7092-4ec7-a897-23e6e5a7b5d2, Timestamp: 2026-02-02T09:22:39.135Z, Process: Click login button
[LOG] Virtual User ID: 65f87b12-f812-4570-a936-b1195e33d2d4, Timestamp: 2026-02-02T09:22:39.623Z, Process: Click login button
test.stepを利用することで、各ステップにかかった時間をカスタムメトリクスに出力することも可能です。Artillery公式サイト - test.step argumentを参照してください。
スクリーンショット保存
エラー発生時や記録したいタイミングでスクリーンショットを保存できます。
async function saveScreenshot(page: Page, vuserId: string) {
await page.screenshot({
path:
".\\tests\\performanceTest\\" +
"PerformanceTest_ErrorScreenshot_" +
vuserId +
"_" +
getNowTimeString() + // 現在時刻のyyyyMMddHHmmssを返す関数(内容割愛)
".png",
fullPage: true, // スクロール可能なページ全体を取得
});
}
スクリーンショットには機密情報等が含まれる可能性があります。取り扱いには十分注意してください。
画面のSpinnerが消えるまで待つ
データ取得中に表示される、画面のローディングを表すSpinnerが消えるまで待ちます。
操作ログ出力と組み合わせて、データ取得が遅延していないか確認できます。
(テスト対象システムで利用しているUIコンポーネントや実装仕様に応じて調整してください)
import { expect } from "@playwright/test";
async function waitForSpinnerDisappear(page: Page, context: Context) {
const spinner = page.getByTestId("spinner");
const content = page.getByTestId("detail-layout");
// 処理開始時間
let start = Date.now();
// spinnerの表示を待つ(spinner表示前に非表示チェックが行われるのを防ぐため、1秒固定で待つ)
await page.waitForTimeout(1000);
// spinnerの非表示を待つ
await expect(spinner).toHaveCount(0, { timeout: timeout });
// spinner計測ログ
let elapsedMs = Date.now() - start;
outputOperationLog(context.vars.$uuid, `spinner wait elapsed=${elapsedMs}ms`);
// contentの表示を待つ
await expect(content).toBeVisible({ timeout: timeout });
}
新しいタブでリンクを開く時
私が実施したパフォーマンステストでは、ユーザー操作シナリオ上、新しいタブでリンクを開く操作がありました。
新しいタブでリンクを開く時は、以下に注意が必要です。
- 新しいタブ(新しいPageオブジェクト)は、Artilleryメトリクスレポートの対象にならない
- 複数タブを表示するとメモリ使用量が多くなる
以下の対応により、これらの問題を回避できます。
- 新しいタブで開いたURLをコピーして、元のタブで開き直す
- 新しいタブを閉じる
// 新しいタブ
const [newPage] = await Promise.all([
// 新しいタブを待つ
page.context().waitForEvent("page", { timeout: timeout }),
// 新しいタブを開くリンクをクリック
page.click(`a[href="${context.vars.targetURL}/〇〇〇"]`),
]);
// 新しいタブの読み込みを待つ
await newPage.waitForURL("**/===");
// 新しいタブで開いたURLコピー
const newUrl = newPage.url();
// 新しいタブを閉じる
await newPage.close();
// 元のタブで開き直す
await page.goto(newUrl);
Cookie情報で認証する
Artillery (Playwright engine)では、クリーンな状態のブラウザが起動するため、普段自分がアクセスできているサイトも認証エラーになることがあります。
その場合は、Cookieを取得して使用することで認証可能な場合があります。
Cookieの取り扱いにはセキュリティリスクがあるため、流出やリポジトリへ誤ってコミットなどしないよう、十分に注意してください。
他の方法で認証可能な場合はそちらを優先されることをおすすめします。
最終手段として活用ください。
EdgeアドオンのCookie-Editorなどを利用してコピーしたCookieを読み込んで使用します。
型が合わない場合は事前に整形します。
const cookiesFromCookieEditor = [/* 外部ファイルなどから読み込みを推奨 */];
interface Context {
vars: {
// フォーマット整形されたcookie
formattedCookies: {
domain: string;
expires: number | undefined;
httpOnly: boolean;
name: string;
path: string;
sameSite: "Strict" | "Lax" | "None";
secure: boolean;
value: string;
}[];
};
}
async function beforePerformanceTest(page: Page, context: Context) {
// Cookie-Editorで取得したcookieのフォーマット整形
context.vars.formattedCookies = cookiesFromCookieEditor.map((cookie) => ({
domain: cookie.domain,
expires: cookie.expirationDate,
httpOnly: cookie.httpOnly,
name: cookie.name,
path: cookie.path,
sameSite: (cookie.sameSite === "strict"
? "Strict"
: cookie.sameSite === "lax"
? "Lax"
: "None") as "Strict" | "Lax" | "None",
secure: cookie.secure,
value: cookie.value,
}));
}
async function performanceTest(page: Page, context: Context) {
// 最初にCookieを追加する
await page.context().addCookies(context.vars.formattedCookies)
await page.goto("https://~~~");
}
おわりに
Artillery (Playwright engine)でパフォーマンステストスクリプトを作成する際の実践Tipsを紹介しました。
少しでも皆さんのお役に立てれば光栄です。
今後またパフォーマンステストを実施するチャンスがあれば、以下に取り組みたいと思います。
- 大規模なパフォーマンステストをクラウド上で実行
- Artillery Cloudを利用した視覚的で高度なレポートの使用
ありがとうございました。