Zephyrのテストサイクルから特定のフォルダ配下のテストケースをまとめて選択・除外する機能をChrome拡張に追加してみた
こんにちは。ダイの大冒険エンジョイ勢のbun913と申します。
私は以前Zephyrというテストマネジメントツールをより便利に使うためのChrome拡張機能を公開していました。
以前の記事はこちらです。
私も改善要望などをお出ししていますが、すぐに改善いただくことは現実的に難しい問題をその間だけでも便利に周りの方に使っていただけるように機能追加をする目的でこれを作成・メンテナンスしています。(もし対応いただけたらその機能は削除をしていこうと考えています)
最初に公開した機能は2つですが、今回実は2回目のアップデートとして、私の周りの人が待望していた機能を追加してみました。
早速デモ
主にZephyrユーザーの方に向けた説明になっておりますが、後ほど背景などはご説明します。
Test Cycle に登録されているテストケースを 特定のフォルダ配下のケースだけまとめて選択して除外したり、情報を変更したい と思ったことはありませんか?
こちらの拡張(v0.3.0)を導入すると
- 各テストケースの名前の下にフォルダのパスが表示される
- また
Select by folderというテキストボックスにフォルダパスを入れると、前方一致で特定のフォルダ配下のテストケースをまとめて 40件まで 一括で選択できる
以下は app1/ というフォルダ配下のテストケースを一気に選択しています。このフォルダはネストしていても対応できるため app1/general_user や app1/permission/admin といったサブフォルダ配下のテストケースもまとめて選択できます。

機能作成の背景
Zephyrには Test Cycle という形で複数のテストケースをまとめて、実行結果を管理できるようなリソースがあります。 Test Cycle に登録されたテストケースの情報を一括で変更したり、まとめて削除(除外)できて便利です。
ですが、(少なくとも私は)2点ほどつらさを感じています。
- 本来はテストケースはフォルダというリソースで構造化して管理できるのですが、この画面にはフォルダの情報が表示されていない
- そのため、たとえば「これらのテストケースは今回実施しなくてよかったな」や「まとめて情報を変更したいな」と思った時に狙ったテストケース群を選択できません
- 変更したいケースを一括で選択できるのですが、50件より多いリソースを一括選択できたにもかかわらず、まとめて削除といった使い方ができない
- 51件以上を選択した場合、自分で50件以下になるまでチェックを外していく必要があるようです
そこでこの拡張機能では以下2点に対応しました。
- まず以下のようにシンプルにテストケースのNameの下に所属するフォルダのパスを表示しました

- 次に以下のようなテストケース一括選択のためのテキストボックスとボタンを用意しました

あとは上でご紹介したデモgifのとおり、特定のフォルダ配下のテストケースをまとめて選択できるようにしてみました。もちろんこのあと Delete ボタンで一括で Test Cycle からテストケースをまとめて除外することもできます。

機能の仕組み
まずは、フォルダの情報を各テストケースの行に追加していきます。
/**
* Add folder path to a grid row (append to Name cell)
*/
function addFolderPathToRow(
row: HTMLElement,
folderPathMap: Map<number, string>,
testRunItemsMap: Map<number, number | null>,
): void {
// Check if already processed
if (row.querySelector(".zephyr-extension-folder-path")) {
return;
}
// Get test run item ID from data-row-id attribute
const dataRowId = row.getAttribute("data-row-id");
if (!dataRowId) {
return;
}
const testRunItemId = Number.parseInt(dataRowId, 10);
const folderId = testRunItemsMap.get(testRunItemId);
// Get folder path
if (!folderId || !folderPathMap.has(folderId)) {
return;
}
const folderPath = folderPathMap.get(folderId) || "";
// Find the name cell
const nameCell = row.querySelector('td[data-cell-id="name"]');
if (!nameCell) {
return;
}
// Create folder path label (on new line)
const folderPathLabel = document.createElement("span");
folderPathLabel.className = "zephyr-extension-folder-path";
folderPathLabel.setAttribute("data-folder-path", folderPath);
folderPathLabel.style.cssText = `
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #888;
margin-top: 2px;
`;
folderPathLabel.title = folderPath; // Full path on hover
// Add extension icon
const icon = document.createElement("img");
icon.src = chrome.runtime.getURL("icon.png");
icon.style.cssText = `
width: 12px;
height: 12px;
flex-shrink: 0;
`;
const text = document.createElement("span");
text.textContent = folderPath;
folderPathLabel.appendChild(icon);
folderPathLabel.appendChild(text);
// Append to name cell
nameCell.appendChild(folderPathLabel);
}
なんとも泥臭いですが、既存のコードに手を加えることはできないため、なんとか既存のページに後付けで機能を付与しています。
同様にボタンなども追加しています。
/**
* Add filter UI (input + button)
*/
function addFilterUI(): void {
// Check if already added
if (document.getElementById("zephyr-extension-folder-filter")) {
return;
}
// Find the grid header using data-column-id attribute
const headerColumn = document.querySelector("[data-column-id]");
if (!headerColumn || !headerColumn.parentElement) {
logger.debug("Grid header not found for filter UI");
return;
}
const headerContainer = headerColumn.parentElement;
// Create filter container
const filterContainer = document.createElement("div");
filterContainer.id = "zephyr-extension-folder-filter";
filterContainer.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
`;
// Add extension icon
const icon = document.createElement("img");
icon.src = chrome.runtime.getURL("icon.png");
icon.style.cssText = `
width: 16px;
height: 16px;
`;
// Create label
const label = document.createElement("span");
label.textContent = "Select by folder:";
label.style.cssText = `
font-size: 12px;
font-weight: 500;
color: #333;
`;
// Create input
const input = document.createElement("input");
input.type = "text";
input.id = "zephyr-extension-folder-filter-input";
input.placeholder = "e.g. app1/general_user/login";
input.style.cssText = `
padding: 6px 12px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 12px;
width: 300px;
`;
// Create button
const button = document.createElement("button");
button.textContent = "Select Matching (max 40)";
button.style.cssText = `
padding: 6px 12px;
background-color: #0052cc;
color: white;
border: none;
border-radius: 3px;
font-size: 12px;
cursor: pointer;
`;
button.addEventListener("click", handleFilterClick);
button.addEventListener("mouseenter", () => {
button.style.backgroundColor = "#0065ff";
});
button.addEventListener("mouseleave", () => {
button.style.backgroundColor = "#0052cc";
});
filterContainer.appendChild(icon);
filterContainer.appendChild(label);
filterContainer.appendChild(input);
filterContainer.appendChild(button);
// Insert before the header container
headerContainer.insertAdjacentElement("beforebegin", filterContainer);
logger.debug("Filter UI added");
}
あとはご想像の通りですが、フォルダパスをテストケースに付与した際に持たせたフォルダパスの情報を元に入力されたフォルダパスと前方一致するテストケースのチェックボックスを有効にする方法をとっています。
また、地味に40件までを一気に選択することにして、後で手動で数件追加しても50件制限に収まるよう余裕を持たせました。
まとめ
- Zephyrというテストマネジメントツールについて、Chrome拡張機能を公開していました
- 要望を出しているものの、その間にもできるだけ多くの人に便利に使ってもらえるように機能を追加しています
- 今回は地味ですが、特定のフォルダ配下に属するテストケースを一括選択する機能を実装しました
公式に対応いただければ順次機能を削除していくつもりですが、それまで何か不便なことを見つけた場合は、随時機能を追加していきたいと思います。
何かフィードバックがあればこちらのリポジトリに Issue を作成いただけますと、対応ができるかもしれません。もし興味があれば Chrome拡張やこの記事やリポジトリにスターをいただけると、励みになりますので気が向けばお願いいたします。
以上、最後までお読みいただいてありがとうございました。
Discussion