Skip to content

Commit 49e2c7c

Browse files
sevenc-nanashigithub-actions[bot]Hiroshiba
authored
Add: VRTを追加 (#1688)
* Add: voicevox_engineをsubmoduleとして追加 * Add: とりあえず動くように * Add: submodules指定を追加 * Change: OS名をファイル名から削除 * Fix: アイコンを修正 * Change: osをartifactの名前に追加 * Change: スクショをGitHub Actionsで更新するように * [update-snapshots] * [update snapshots] * Fix: electronとbrowserが逆だったので修正 [update snapshots] * Change: コミットを一つのjobに分離 * Add: needsを追加 [update snapshots] * Change: 検出方法を変更 [update snapshots] * (スナップショットを更新) [skip ci] * Delete: 使われてないスクショを削除 * Add: メッセージを追加 * Add: 説明を追加 * Code: コメントを変更 * Add: ヒントを追加 * Fix: pull_requestイベントで動くように * Change: テスト用アセットを変更 [update snapshots] * (スナップショットを更新) [skip ci] * Fix: markdownlintのエラーを修正 * Change: skip ciしないように * Change: モック画像を変更 [update snapshots] * Update: playwrightを更新 * Fix: ファイル名を指定 [update snapshots] * (スナップショットを更新) * (CI用) * Add: 1%の誤差は許容するように * Update: スナップショットを更新 [update snapshots] * (CI用) * Change: 許容誤差を0.1%に * Delete: xvfb-runを削除 * Change: Windowsに限定 * Improve: READMEの記述を改善 Co-authored-by: Hiroshiba <[email protected]> * Change: スタイル名を一意に Co-authored-by: Hiroshiba <[email protected]> * Delete: 不要な指定を削除 * Change: updateSnapshots -> shouldUpdateSnapshots * (スクショ更新) [update-snapshots] * to shouldUpdateSnapshots [update snapshots] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Hiroshiba <[email protected]> Co-authored-by: Hiroshiba Kazuyuki <[email protected]>
1 parent 9d9cb7a commit 49e2c7c

15 files changed

+242
-29
lines changed

.github/workflows/test.yml

+58-5
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,34 @@ jobs:
8484
sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port=50021"],|' .env.test
8585
cp .env.test .env
8686
87+
- name: Check if commit message includes [update snapshots]
88+
id: check-whether-to-update-snapshots
89+
uses: actions/github-script@v7
90+
with:
91+
github-token: ${{ secrets.GITHUB_TOKEN }}
92+
script: |
93+
const commits = ${{ toJson(github.event.commits) }};
94+
if (!commits) {
95+
// pull_request などでコミットがない場合はスキップ
96+
core.setOutput("shouldUpdateSnapshots", false);
97+
process.exit(0);
98+
}
99+
const shouldUpdateSnapshots = commits.some((commit) =>
100+
commit.message.toLowerCase().includes("[update snapshots]")
101+
);
102+
core.setOutput("shouldUpdateSnapshots", shouldUpdateSnapshots);
103+
console.log(`shouldUpdateSnapshots: ${shouldUpdateSnapshots}`);
104+
87105
- name: Run npm run test:browser-e2e
88106
run: |
89107
if [ -n "${{ runner.debug }}" ]; then
90108
export DEBUG="pw:browser*"
91109
fi
92-
if [[ ${{ matrix.os }} == ubuntu-* ]]; then
93-
xvfb-run --auto-servernum npm run test:browser-e2e
94-
else
95-
npm run test:browser-e2e
110+
ARGS=""
111+
if [[ ${{ steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots }} == 'true' ]]; then
112+
ARGS="--update-snapshots"
96113
fi
114+
npm run test:browser-e2e -- $ARGS
97115
98116
- name: Run npm run test:electron-e2e
99117
run: |
@@ -110,9 +128,44 @@ jobs:
110128
if: failure()
111129
uses: actions/upload-artifact@v3
112130
with:
113-
name: playwright-report
131+
name: playwright-report-${{ matrix.os }}
114132
path: playwright-report
115133

134+
- name: Upload updated snapshots to artifact
135+
if: steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots == 'true'
136+
uses: actions/upload-artifact@v4
137+
with:
138+
name: updated-snapshots-${{ matrix.os }}
139+
path: tests/e2e/browser/*-snapshots/*
140+
141+
commit-snapshots:
142+
runs-on: ubuntu-latest
143+
permissions:
144+
contents: write
145+
needs:
146+
- e2e-test
147+
steps:
148+
- uses: actions/checkout@v3
149+
150+
- name: Download artifacts
151+
uses: actions/download-artifact@v4
152+
with:
153+
pattern: updated-snapshots-*
154+
path: tests/e2e/browser
155+
merge-multiple: true
156+
157+
- name: Commit updated snapshots
158+
run: |
159+
git config --global user.name "github-actions[bot]"
160+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
161+
if [ -n "$(git status --porcelain)" ]; then
162+
git add tests/e2e/browser
163+
git commit -m "(スナップショットを更新)"
164+
git push
165+
else
166+
echo "No changes to commit"
167+
fi
168+
116169
lint:
117170
runs-on: ubuntu-latest
118171
steps:

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ npx playwright codegen http://localhost:5173/#/home --viewport-size=800,600
113113

114114
詳細は [Playwright ドキュメントの Test generator](https://playwright.dev/docs/codegen-intro) を参照してください。
115115

116+
#### スクリーンショットの更新
117+
118+
ブラウザ End to End テストでは Visual Regression Testing を行っています。
119+
以下の手順でスクリーンショットを更新できます:
120+
121+
1. フォークしたリポジトリの設定で GitHub Actions を有効にします。
122+
2. リポジトリの設定の Actions > General > Workflow permissions で Read and write permissions を選択します。
123+
3. `[update snapshots]` という文字列をコミットメッセージに含めてコミットします。
124+
125+
```bash
126+
git commit -m "UIを変更 [update snapshots]"
127+
```
128+
129+
4. Github Workflow が完了すると、更新されたスクリーンショットがコミットされます。
130+
116131
### Electron End to End テスト
117132

118133
Electron の機能が必要な、エンジン起動・終了などを含めた End to End テストを実行します。

package-lock.json

+23-23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
},
7171
"devDependencies": {
7272
"@openapitools/openapi-generator-cli": "2.7.0",
73-
"@playwright/test": "1.39.0",
73+
"@playwright/test": "1.40.1",
7474
"@quasar/vite-plugin": "1.3.0",
7575
"@types/async-lock": "1.4.0",
7676
"@types/clone-deep": "4.0.1",

playwright.config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ const config: PlaywrightTestConfig = {
6060
* For example in `await expect(locator).toHaveText();`
6161
*/
6262
timeout: 5 * 1000,
63+
toHaveScreenshot: {
64+
maxDiffPixelRatio: 0.001,
65+
},
6366
},
6467
// ファイルシステムが関連してくるので、Electronテストでは並列化しない
6568
fullyParallel: !isElectron,

tests/e2e/browser/assets/icon_1.png

6.66 KB
Loading

tests/e2e/browser/assets/icon_2.png

8.82 KB
Loading

tests/e2e/browser/assets/icon_3.png

21 KB
Loading

tests/e2e/browser/assets/icon_4.png

20.7 KB
Loading
7.14 KB
Loading
8.35 KB
Loading
21.3 KB
Loading
19.3 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import path from "path";
2+
import fs from "fs/promises";
3+
import { test, expect } from "@playwright/test";
4+
import { gotoHome, navigateToMain } from "../navigators";
5+
import {
6+
Speaker,
7+
SpeakerFromJSON,
8+
SpeakerInfo,
9+
SpeakerInfoFromJSON,
10+
SpeakerInfoToJSON,
11+
SpeakerToJSON,
12+
} from "@/openapi";
13+
14+
let speakerImages: {
15+
portrait: string;
16+
icon: string;
17+
}[];
18+
19+
/**
20+
* 差し替え用の立ち絵・アイコンを取得する。
21+
*/
22+
async function getSpeakerImages(): Promise<
23+
{
24+
portrait: string;
25+
icon: string;
26+
}[]
27+
> {
28+
if (!speakerImages) {
29+
const assetsPath = path.resolve(__dirname, "assets");
30+
const images = await fs.readdir(assetsPath);
31+
const icons = images.filter((image) => image.startsWith("icon"));
32+
icons.sort(
33+
(a, b) =>
34+
parseInt(a.split(".")[0].split("_")[1]) -
35+
parseInt(b.split(".")[0].split("_")[1])
36+
);
37+
speakerImages = await Promise.all(
38+
icons.map(async (iconPath) => {
39+
const portraitPath = iconPath.replace("icon_", "portrait_");
40+
const portrait = await fs.readFile(
41+
path.join(assetsPath, portraitPath),
42+
"base64"
43+
);
44+
const icon = await fs.readFile(
45+
path.join(assetsPath, iconPath),
46+
"base64"
47+
);
48+
49+
return { portrait, icon };
50+
})
51+
);
52+
}
53+
return speakerImages;
54+
}
55+
56+
test.beforeEach(async ({ page }) => {
57+
let speakers: Speaker[];
58+
const speakerImages = await getSpeakerImages();
59+
// Voicevox Nemo EngineでもVoicevox Engineでも同じ結果が選られるように、
60+
// GET /speakers、GET /speaker_infoの話者名、スタイル名、画像を差し替える。
61+
await page.route(/\/speakers$/, async (route) => {
62+
const response = await route.fetch();
63+
const json: Speaker[] = await response
64+
.json()
65+
.then((json) => json.map(SpeakerFromJSON));
66+
let i = 0;
67+
for (const speaker of json) {
68+
i++;
69+
speaker.name = `Speaker ${i}`;
70+
let j = 0;
71+
for (const style of speaker.styles) {
72+
j++;
73+
style.name = `Style ${i}-${j}`;
74+
}
75+
}
76+
speakers = json;
77+
await route.fulfill({
78+
status: 200,
79+
headers: {
80+
"Access-Control-Allow-Origin": "*",
81+
"Content-Type": "application/json",
82+
},
83+
body: JSON.stringify(json.map(SpeakerToJSON)),
84+
});
85+
});
86+
await page.route(/\/speaker_info\?/, async (route) => {
87+
if (!speakers) {
88+
// Unreachableのはず
89+
throw new Error("speakers is not initialized");
90+
}
91+
const url = new URL(route.request().url());
92+
const speakerUuid = url.searchParams.get("speaker_uuid");
93+
if (!speakerUuid) {
94+
throw new Error("speaker_uuid is not set");
95+
}
96+
const response = await route.fetch();
97+
const json: SpeakerInfo = await response.json().then(SpeakerInfoFromJSON);
98+
const speakerIndex = speakers.findIndex(
99+
(speaker) => speaker.speakerUuid === speakerUuid
100+
);
101+
if (speakerIndex === -1) {
102+
throw new Error(`speaker_uuid=${speakerUuid} is not found`);
103+
}
104+
const image = speakerImages[speakerIndex % speakerImages.length];
105+
json.portrait = image.portrait;
106+
for (const style of json.styleInfos) {
107+
style.icon = image.icon;
108+
if ("portrait" in style) {
109+
delete style.portrait;
110+
}
111+
}
112+
await route.fulfill({
113+
status: 200,
114+
headers: {
115+
"Access-Control-Allow-Origin": "*",
116+
"Content-Type": "application/json",
117+
},
118+
body: JSON.stringify(SpeakerInfoToJSON(json)),
119+
});
120+
});
121+
});
122+
test.beforeEach(gotoHome);
123+
124+
test("メイン画面の表示", async ({ page }) => {
125+
test.skip(process.platform !== "win32", "Windows以外のためスキップします");
126+
await navigateToMain(page);
127+
128+
// eslint-disable-next-line no-constant-condition
129+
while (true) {
130+
await page.locator(".audio-cell:nth-child(1) .q-field").click();
131+
await page.waitForTimeout(100);
132+
if (
133+
(await page
134+
.locator(".character-portrait-wrapper .character-name")
135+
.innerText()) !== "(表示エラー)" &&
136+
(await page.locator(".character-portrait-wrapper .loading").count()) === 0
137+
) {
138+
break;
139+
}
140+
}
141+
await expect(page).toHaveScreenshot("メイン画面.png");
142+
});

0 commit comments

Comments
 (0)