Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
30c6540
Add: voicevox_engineをsubmoduleとして追加
sevenc-nanashi Jan 4, 2024
56fda0f
Add: とりあえず動くように
sevenc-nanashi Jan 4, 2024
45b11a4
Add: submodules指定を追加
sevenc-nanashi Jan 4, 2024
7f42bf5
Change: OS名をファイル名から削除
sevenc-nanashi Jan 4, 2024
b41da3a
Fix: アイコンを修正
sevenc-nanashi Jan 4, 2024
496fd97
Change: osをartifactの名前に追加
sevenc-nanashi Jan 4, 2024
d97d77e
Change: スクショをGitHub Actionsで更新するように
sevenc-nanashi Jan 4, 2024
0d36c2c
[update-snapshots]
sevenc-nanashi Jan 4, 2024
9ae7800
[update snapshots]
sevenc-nanashi Jan 4, 2024
290fb11
Fix: electronとbrowserが逆だったので修正
sevenc-nanashi Jan 4, 2024
5213d51
Change: コミットを一つのjobに分離
sevenc-nanashi Jan 4, 2024
1a74aea
Add: needsを追加
sevenc-nanashi Jan 4, 2024
38f51ee
Change: 検出方法を変更
sevenc-nanashi Jan 4, 2024
8447c9d
(スナップショットを更新)
github-actions[bot] Jan 4, 2024
d30efed
Delete: 使われてないスクショを削除
sevenc-nanashi Jan 4, 2024
a8c88a1
Add: メッセージを追加
sevenc-nanashi Jan 4, 2024
61e9447
Add: 説明を追加
sevenc-nanashi Jan 5, 2024
6ee6829
Code: コメントを変更
sevenc-nanashi Jan 5, 2024
d8bacab
Add: ヒントを追加
sevenc-nanashi Jan 5, 2024
651e524
Fix: pull_requestイベントで動くように
sevenc-nanashi Jan 5, 2024
c5af75f
Change: テスト用アセットを変更
sevenc-nanashi Jan 5, 2024
d83d58d
(スナップショットを更新)
github-actions[bot] Jan 5, 2024
2336571
Fix: markdownlintのエラーを修正
sevenc-nanashi Jan 5, 2024
b9616f1
Change: skip ciしないように
sevenc-nanashi Jan 5, 2024
d7bbc38
Change: モック画像を変更
sevenc-nanashi Jan 5, 2024
fae289c
Update: playwrightを更新
sevenc-nanashi Jan 5, 2024
16ec9d3
Fix: ファイル名を指定
sevenc-nanashi Jan 5, 2024
e8ecaec
(スナップショットを更新)
github-actions[bot] Jan 5, 2024
857efe0
(CI用)
sevenc-nanashi Jan 5, 2024
e209054
Add: 1%の誤差は許容するように
sevenc-nanashi Jan 6, 2024
3adb508
Update: スナップショットを更新
sevenc-nanashi Jan 6, 2024
28a6c14
(CI用)
sevenc-nanashi Jan 6, 2024
fc6a9de
Change: 許容誤差を0.1%に
sevenc-nanashi Jan 6, 2024
218e959
Delete: xvfb-runを削除
sevenc-nanashi Jan 8, 2024
bee108e
Change: Windowsに限定
sevenc-nanashi Jan 16, 2024
267cd24
Merge: main -> add/mock-speakers
sevenc-nanashi Jan 16, 2024
8d3e87e
Improve: READMEの記述を改善
sevenc-nanashi Jan 17, 2024
7b300ee
Change: スタイル名を一意に
sevenc-nanashi Jan 17, 2024
383928e
Delete: 不要な指定を削除
sevenc-nanashi Jan 17, 2024
392b88a
Change: updateSnapshots -> shouldUpdateSnapshots
sevenc-nanashi Jan 17, 2024
1840c7c
(スクショ更新)
sevenc-nanashi Jan 17, 2024
759f80c
to shouldUpdateSnapshots [update snapshots]
Hiroshiba Jan 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,34 @@ jobs:
sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port=50021"],|' .env.test
cp .env.test .env

- name: Check if commit message includes [update snapshots]
id: check-whether-to-update-snapshots
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const commits = ${{ toJson(github.event.commits) }};
if (!commits) {
// pull_request などでコミットがない場合はスキップ
core.setOutput("shouldUpdateSnapshots", false);
process.exit(0);
}
const shouldUpdateSnapshots = commits.some((commit) =>
commit.message.toLowerCase().includes("[update snapshots]")
);
core.setOutput("shouldUpdateSnapshots", shouldUpdateSnapshots);
console.log(`shouldUpdateSnapshots: ${shouldUpdateSnapshots}`);

- name: Run npm run test:browser-e2e
run: |
if [ -n "${{ runner.debug }}" ]; then
export DEBUG="pw:browser*"
fi
if [[ ${{ matrix.os }} == ubuntu-* ]]; then
xvfb-run --auto-servernum npm run test:browser-e2e
else
npm run test:browser-e2e
ARGS=""
if [[ ${{ steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots }} == 'true' ]]; then
ARGS="--update-snapshots"
fi
npm run test:browser-e2e -- $ARGS

- name: Run npm run test:electron-e2e
run: |
Expand All @@ -110,9 +128,44 @@ jobs:
if: failure()
uses: actions/upload-artifact@v3
with:
name: playwright-report
name: playwright-report-${{ matrix.os }}
path: playwright-report

- name: Upload updated snapshots to artifact
if: steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots == 'true'
uses: actions/upload-artifact@v4
with:
name: updated-snapshots-${{ matrix.os }}
path: tests/e2e/browser/*-snapshots/*

commit-snapshots:
runs-on: ubuntu-latest
permissions:
contents: write
needs:
- e2e-test
steps:
- uses: actions/checkout@v3
Comment on lines +141 to +148
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今気づいたのですが、このjobを実行するかどうかをshouldUpdateSnapshotsの出力から判定できるかもですね。
if: steps.check-whether-to-update-snapshots.outputs.shouldUpdateSnapshots == 'true'をここに書くとかで。


- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: updated-snapshots-*
path: tests/e2e/browser
merge-multiple: true

- name: Commit updated snapshots
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
if [ -n "$(git status --porcelain)" ]; then
git add tests/e2e/browser
git commit -m "(スナップショットを更新)"
git push
else
echo "No changes to commit"
fi

lint:
runs-on: ubuntu-latest
steps:
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ npx playwright codegen http://localhost:5173/#/home --viewport-size=800,600

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

#### スクリーンショットの更新

ブラウザ End to End テストでは Visual Regression Testing を行っています。
以下の手順でスクリーンショットを更新できます:

1. フォークしたリポジトリの設定で GitHub Actions を有効にします。
2. リポジトリの設定の Actions > General > Workflow permissions で Read and write permissions を選択します。
3. `[update snapshots]` という文字列をコミットメッセージに含めてコミットします。

```bash
git commit -m "UIを変更 [update snapshots]"
```

4. Github Workflow が完了すると、更新されたスクリーンショットがコミットされます。

### Electron End to End テスト

Electron の機能が必要な、エンジン起動・終了などを含めた End to End テストを実行します。
Expand Down
46 changes: 23 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "2.7.0",
"@playwright/test": "1.39.0",
"@playwright/test": "1.40.1",
"@quasar/vite-plugin": "1.3.0",
"@types/async-lock": "1.4.0",
"@types/clone-deep": "4.0.1",
Expand Down
3 changes: 3 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const config: PlaywrightTestConfig = {
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5 * 1000,
toHaveScreenshot: {
maxDiffPixelRatio: 0.001,
},
},
// ファイルシステムが関連してくるので、Electronテストでは並列化しない
fullyParallel: !isElectron,
Expand Down
Binary file added tests/e2e/browser/assets/icon_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/icon_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/icon_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/icon_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/portrait_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/portrait_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/portrait_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/e2e/browser/assets/portrait_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
142 changes: 142 additions & 0 deletions tests/e2e/browser/スクリーンショット.spec.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基本的に多分画面ごとにテストがあって、その中で1つのテストとしてスクリーンショットテストがあるのかなと想像してます。
なのでスクリーンショット.spec.tsは将来的に解体されるかも?

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import path from "path";
import fs from "fs/promises";
import { test, expect } from "@playwright/test";
import { gotoHome, navigateToMain } from "../navigators";
import {
Speaker,
SpeakerFromJSON,
SpeakerInfo,
SpeakerInfoFromJSON,
SpeakerInfoToJSON,
SpeakerToJSON,
} from "@/openapi";

let speakerImages: {
portrait: string;
icon: string;
}[];

/**
* 差し替え用の立ち絵・アイコンを取得する。
*/
async function getSpeakerImages(): Promise<
{
portrait: string;
icon: string;
}[]
> {
if (!speakerImages) {
const assetsPath = path.resolve(__dirname, "assets");
const images = await fs.readdir(assetsPath);
const icons = images.filter((image) => image.startsWith("icon"));
icons.sort(
(a, b) =>
parseInt(a.split(".")[0].split("_")[1]) -
parseInt(b.split(".")[0].split("_")[1])
);
speakerImages = await Promise.all(
icons.map(async (iconPath) => {
const portraitPath = iconPath.replace("icon_", "portrait_");
const portrait = await fs.readFile(
path.join(assetsPath, portraitPath),
"base64"
);
const icon = await fs.readFile(
path.join(assetsPath, iconPath),
"base64"
);

return { portrait, icon };
})
);
}
return speakerImages;
}

test.beforeEach(async ({ page }) => {
let speakers: Speaker[];
const speakerImages = await getSpeakerImages();
// Voicevox Nemo EngineでもVoicevox Engineでも同じ結果が選られるように、
// GET /speakers、GET /speaker_infoの話者名、スタイル名、画像を差し替える。
await page.route(/\/speakers$/, async (route) => {
const response = await route.fetch();
const json: Speaker[] = await response
.json()
.then((json) => json.map(SpeakerFromJSON));
let i = 0;
for (const speaker of json) {
i++;
speaker.name = `Speaker ${i}`;
let j = 0;
for (const style of speaker.styles) {
j++;
style.name = `Style ${i}-${j}`;
}
}
speakers = json;
await route.fulfill({
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
body: JSON.stringify(json.map(SpeakerToJSON)),
});
});
await page.route(/\/speaker_info\?/, async (route) => {
if (!speakers) {
// Unreachableのはず
throw new Error("speakers is not initialized");
}
const url = new URL(route.request().url());
const speakerUuid = url.searchParams.get("speaker_uuid");
if (!speakerUuid) {
throw new Error("speaker_uuid is not set");
}
const response = await route.fetch();
const json: SpeakerInfo = await response.json().then(SpeakerInfoFromJSON);
const speakerIndex = speakers.findIndex(
(speaker) => speaker.speakerUuid === speakerUuid
);
if (speakerIndex === -1) {
throw new Error(`speaker_uuid=${speakerUuid} is not found`);
}
const image = speakerImages[speakerIndex % speakerImages.length];
json.portrait = image.portrait;
for (const style of json.styleInfos) {
style.icon = image.icon;
if ("portrait" in style) {
delete style.portrait;
}
}
await route.fulfill({
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
body: JSON.stringify(SpeakerInfoToJSON(json)),
});
});
});
test.beforeEach(gotoHome);

test("メイン画面の表示", async ({ page }) => {
test.skip(process.platform !== "win32", "Windows以外のためスキップします");
await navigateToMain(page);

// eslint-disable-next-line no-constant-condition
while (true) {
Comment on lines +128 to +129
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ一生テストが終わらないとかありそうなので、20回ぐらいトライしたらエラーにするみたいな処理書いてもいいかも?
そういうリトライ用の便利関数をutilityに実装しちゃうとかどうでしょう。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一生テストが終わらない

Playwright側でタイムアウトしてくれるので大丈夫だと思います。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほどです。
タイムアウトが30秒くらいだったとして、本来数秒でわかる問題を発見するのに数十秒かかるのは、まあ避けといたほうが良い気もしました。

ESLintの警告を無視しているのも気になったのですが、調べた感じこれはデフォルトの挙動を変えないかという議論中でした。
eslint/eslint#17807

await page.locator(".audio-cell:nth-child(1) .q-field").click();
await page.waitForTimeout(100);
if (
(await page
.locator(".character-portrait-wrapper .character-name")
.innerText()) !== "(表示エラー)" &&
(await page.locator(".character-portrait-wrapper .loading").count()) === 0
) {
break;
}
}
await expect(page).toHaveScreenshot("メイン画面.png");
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.