テストケース名に合わせた添付ファイルの命名
スクリーンショット、録画、ログに、それらを生成したテストケースの名前を付けると、BitriseはTestsタブで該当するテストの下にそれらを表示します。
これには、Deploy to Bitrise.io Stepのバージョン2.26.0以降が必要です。Export test results to Test Reports Stepを使用する場合は、バージョン1.3.0以降である必要があります。
次の作業が必要です。
ファイル名の構造についてClick to copy link
ファイル名は、次の構造にします。
<classname>__<name>[__run<k>]__<label>.<ext>
たとえば、com.example.LoginTest__emptyState__1.pngです。
正しいファイル名にするには、次の点に注意してください。
- JUnit XMLの
<testcase>要素からclassnameとnameをコピーし、" * / : < > ? \ |のいずれかの文字を_に置き換えます。BitriseはJUnit XML内の名前にも同じ置換を適用するため、どちらの表記でも一致します。 - 必ずカウンターなどのラベルを付けます。ラベルに
__を含めることはできず、_で始めることもできません。 - 同じテストを複数のデバイスで実行する場合は、ラベルにデバイスを含めます(例:
api34_1)。含めない場合、同じ名前のファイルはスキップされ、添付ファイルは表示されません。 - 同じテストケースの後続の実行にファイルを添付するには、ラベルの前に
__run2、__run3などを追加します。
テストツールの設定Click to copy link
命名の設定は、テスト構成で一度だけ行います。次の例は、一般的なテストツールでの設定方法を示しています。
- Espresso
- Paparazzi
- Roborazzi
- Detox
- Playwright
Espressoのチェックが失敗したときにスクリーンショットを撮るRunListenerを登録します。
// app/build.gradle.kts
android {
defaultConfig {
testInstrumentationRunnerArguments["listener"] = "com.example.testing.BitriseScreenshotListener"
testInstrumentationRunnerArguments["useTestStorageService"] = "true"
}
}
dependencies {
androidTestUtil("androidx.test.services:test-services:1.6.0")
}
// app/src/androidTest/java/com/example/testing/BitriseScreenshotListener.kt
package com.example.testing
private fun clean(value: String) = value.replace(Regex("""["*/:<>?\\|]"""), "_")
class BitriseScreenshotListener : RunListener() {
override fun testStarted(test: Description) {
val default = DefaultFailureHandler(InstrumentationRegistry.getInstrumentation().targetContext)
Espresso.setFailureHandler { error, viewMatcher ->
val screen = Resources.getSystem().displayMetrics.run { "${widthPixels}x$heightPixels" }
val name = "${clean(test.className)}__${clean(test.methodName)}__api${Build.VERSION.SDK_INT}_${screen}_failure"
takeScreenshot().writeToTestStorage(name)
default.handle(error, viewMatcher)
}
}
}
通常のJUnitアサーションでは、スクリーンショットは撮影されません。
スナップショットハンドラーを追加する共通関数を通じて、Paparazziのルールを作成します。このハンドラーは、すべてのスナップショットを添付ファイル名でも保存します。
// src/test/java/com/example/testing/BitrisePaparazzi.kt
package com.example.testing
private fun clean(value: String) = value.replace(Regex("""["*/:<>?\\|]"""), "_")
fun bitrisePaparazzi(deviceConfig: DeviceConfig = DeviceConfig.PIXEL_5) =
Paparazzi(deviceConfig = deviceConfig, snapshotHandler = BitriseSnapshotHandler())
class BitriseSnapshotHandler(
maxPercentDifference: Double = 0.1,
private val delegate: SnapshotHandler =
if (System.getProperty("paparazzi.test.verify") == "true") SnapshotVerifier(maxPercentDifference)
else HtmlReportWriter(),
) : SnapshotHandler by delegate {
override fun newFrameHandler(snapshot: Snapshot, frameCount: Int, fps: Int): SnapshotHandler.FrameHandler {
val frames = delegate.newFrameHandler(snapshot, frameCount, fps)
val test = snapshot.testName
val label = snapshot.name?.replace(Regex("_+"), "_")?.removePrefix("_") ?: "snapshot"
val name = "${clean("${test.packageName}.${test.className}")}__${clean(test.methodName)}__$label"
val file = File(System.getProperty("paparazzi.build.dir"), "bitrise-snapshots/${System.getProperty("bitrise.test.task")}/$name.png")
return object : SnapshotHandler.FrameHandler by frames {
override fun handle(image: BufferedImage) {
file.parentFile.mkdirs()
ImageIO.write(image, "png", file)
frames.handle(image)
}
}
}
}
テストで、Paparazzi(...)をbitrisePaparazzi(...)に置き換えます。次に、モジュールのbuild.gradle.ktsに次の内容を追加します。最初のブロックは、Android Gradleプラグイン8でハンドラーがjava.awtを使用できるようにします。2つ目のブロックは、各テストタスクに専用のフォルダーを割り当て、前回の実行のスナップショットを削除し、現在のスナップショットをbuild cacheに保持します。
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>().configureEach {
if ("UnitTest" in name) compilerOptions.noJdk.set(false)
}
tasks.withType<Test>().configureEach {
val snapshots = layout.buildDirectory.dir("bitrise-snapshots/$name")
systemProperty("bitrise.test.task", name)
outputs.dir(snapshots)
doFirst { snapshots.get().asFile.deleteRecursively() }
}
この例はPaparazzi 1.3.5向けです。Paparazzi 2.0では、HtmlReportWriter(maxPercentDifference = maxPercentDifference)でレポートライターを作成します。
Roborazziを実行するたびに、Roborazziのスクリーンショットを添付ファイル名でコピーします。
// app/build.gradle.kts
val bitriseScreenshots by tasks.registering(Sync::class) {
from(layout.buildDirectory.dir("outputs/roborazzi")) { include("*.png") }
into(layout.buildDirectory.dir("bitrise-screenshots"))
rename("""(.+)\.([^.]+?)(?:_(\d+))?(_actual|_compare)?\.png""", "$1__$2__screenshot$3$4.png")
}
tasks.named { it == "finalizeTestRoborazziDebug" }.configureEach { finalizedBy(bitriseScreenshots) }
Roborazziがスクリーンショットを書き出すのは、recordRoborazziDebug、verifyRoborazziDebug、compareRoborazziDebugのいずれかを実行したときだけです。パラメーター化テストには添付ファイルは付きません。
Detoxの設定で、artifacts.pathBuilderをこのファイルに指定します。このファイルは、すべてのスクリーンショット、動画、ログに、jest-junitがデフォルト設定でテストに付ける名前と同じ形式で名前を付けます。
// e2e/bitrisePathBuilder.js
const path = require('path');
const clean = (value) => value.replace(/["*/:<>?\\|]/g, '_');
module.exports = class BitrisePathBuilder {
constructor({ rootDir }) {
this.rootDir = rootDir;
}
buildPathForTestArtifact(artifactName, testSummary) {
if (!testSummary) return path.join(this.rootDir, artifactName);
const { title, fullName } = testSummary;
const key = clean(`${fullName.slice(0, -title.length).trim()} ${title}`);
const ext = path.extname(artifactName);
const label = path.basename(artifactName, ext).replace(/_+/g, '_').replace(/^_/, '');
return path.join(this.rootDir, `${key}__${key}__${label}${ext}`);
}
};
junitレポーターでincludeProjectInTestNameを有効にし、各テストのスクリーンショットと動画を添付ファイル名でコピーするレポーターを追加します。
// playwright.config.ts
reporter: [
['junit', { outputFile: 'test-results/junit.xml', includeProjectInTestName: true }],
['./bitrise-reporter.ts'],
],
use: { screenshot: 'only-on-failure', video: 'retain-on-failure' },
// bitrise-reporter.ts
const clean = (value: string) => value.replace(/["*/:<>?\\|]/g, '_');
const supported = /\.(png|jpe?g|mp4|webm|ogg|txt|log)$/i;
export default class BitriseAttachments implements Reporter {
onTestEnd(test: TestCase, result: TestResult) {
const [, project, file, ...titles] = test.titlePath();
const key = `${clean(file)}__${clean((project ? `[${project}] ` : '') + titles.join(' › '))}`;
const files = result.attachments.filter((a) => a.path && supported.test(a.path));
files.forEach((a, i) => {
const label = (result.retry ? `retry${result.retry}_` : '') + (i + 1);
const target = path.join('test-attachments', `${key}__${label}${path.extname(a.path!)}`);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.copyFileSync(a.path!, target);
});
}
}
添付ファイルのエクスポートClick to copy link
これには、Deploy to Bitrise.io Stepのバージョン2.26.0以降が必要です。Export test results to Test Reports Stepを使用する場合は、バージョン1.3.0以降である必要があります。
Deploy to Bitrise.io Stepは常に必要です。正しい名前を付けた添付ファイルを、テスト結果と一緒にデプロイします。そのほかに必要なものは、テストを実行するStepによって異なります。
- 公式のBitriseテストSteps: 追加の設定は不要です。Deploy to Bitrise.io Stepが、テスト結果と一緒に添付ファイルをエクスポートします。Stepsの一覧については、公式のBitriseテストStepsからの結果のデプロイを参照してください。
- Other Steps(Scriptやnpm Stepsなど): テストを実行するStepの後に、Export test results to Test Reports Stepを追加します。このStepは添付ファイルを見つけて、Deploy to Bitrise.io Stepがデプロイする場所に移動します。Test result base pathインプットには、テスト結果と添付ファイルの両方を含むフォルダー(たとえば
app/build/)を設定します。Export test results to Test Reports Stepの使い方を参照してください。
添付ファイルが表示されない場合のトラブルシューティングClick to copy link
添付ファイルが表示されない場合は、ビルドログを確認してください。
- Export test results to Test Reports Stepは、複数のテストまたはファイルに一致する名前、無効な
__run番号、またはラベルがない名前について警告をログに出力します。そのほかのスキップされたファイルは、詳細ログを有効にした場合にのみ表示されます。Enable verbose logging?インプットをyesに設定してください。 - Deploy to Bitrise.io Stepは、レポート内のファイルのうち、テストケースにリンクできないものすべてについて警告をログに出力します。