Skip to content

Commit f3722a5

Browse files
authored
Merge pull request #20 from microcmsio/feature/add-e2e-test
PlaywrightでE2Eテストを追加
2 parents d07928e + 17aec61 commit f3722a5

File tree

8 files changed

+277
-1
lines changed

8 files changed

+277
-1
lines changed

.github/workflows/playwright.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Playwright Tests
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
timeout-minutes: 60
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 22
18+
cache: 'npm'
19+
- name: Install dependencies
20+
run: npm ci
21+
- name: Install Playwright Browsers
22+
run: npx playwright install --with-deps
23+
- name: Run Playwright tests
24+
run: npm run test:e2e
25+
env:
26+
MICROCMS_API_KEY: ${{ secrets.MICROCMS_API_KEY }}
27+
MICROCMS_SERVICE_DOMAIN: ${{ secrets.MICROCMS_SERVICE_DOMAIN }}
28+
- uses: actions/upload-artifact@v4
29+
if: ${{ !cancelled() }}
30+
with:
31+
name: playwright-report
32+
path: playwright-report/
33+
retention-days: 30

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ yarn-error.log*
3535
# typescript
3636
*.tsbuildinfo
3737
next-env.d.ts
38+
39+
playwright-report
40+
test-results

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ microCMS 管理画面の URL(https://xxxxxxxx.microcms.io)の xxxxxxxx の
3131
開発環境 → http://localhost:3000
3232
本番環境 → https://xxxxxxxx.vercel.app/ など
3333

34+
### GitHub Actionsへの環境変数の設定
35+
36+
このリポジトリではPlaywrightによるE2Eテストが実装されています。
37+
GitHubに変更をプッシュする、あるいはPull Requestを作成すると自動でテストが実行されます。
38+
39+
利用するにはGitHub Actionsのシークレットへの設定が必要です。
40+
[こちらの手順](https://docs.github.com/ja/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets)に従って、`MICROCMS_API_KEY``MICROCMS_SERVICE_DOMAIN`をシークレットに設定してください。
41+
42+
3443
## 開発の仕方
3544

3645
1. パッケージのインストール

e2e/article-detail.spec.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('記事詳細ページ', () => {
4+
let articleUrl: string;
5+
6+
test.beforeEach(async ({ page }) => {
7+
// まず記事一覧ページに行き、最初の記事のURLを取得
8+
await page.goto('/');
9+
const firstArticleLink = page.locator('a[href*="/articles/"]').first();
10+
await expect(firstArticleLink).toBeVisible();
11+
articleUrl = await firstArticleLink.getAttribute('href') || '/articles/test';
12+
});
13+
14+
test('記事詳細ページが正しく表示される', async ({ page }) => {
15+
await page.goto(articleUrl);
16+
17+
// ページが正常に読み込まれることを確認
18+
await expect(page).not.toHaveTitle('404');
19+
20+
// 記事のコンテンツが存在することを確認(最初に見つかった要素のみ)
21+
const articleContent = page.locator('article').or(
22+
page.locator('[data-testid="article-content"]')
23+
).or(
24+
page.locator('main')
25+
).first();
26+
27+
await expect(articleContent).toBeVisible();
28+
});
29+
30+
test('記事タイトルが表示される', async ({ page }) => {
31+
await page.goto(articleUrl);
32+
33+
// H1タグまたはタイトルが表示されることを確認(最初に見つかった要素のみ)
34+
const title = page.locator('h1').or(
35+
page.locator('[data-testid="article-title"]')
36+
).first();
37+
38+
await expect(title).toBeVisible();
39+
await expect(title).not.toBeEmpty();
40+
});
41+
42+
test('記事の日付が表示される', async ({ page }) => {
43+
await page.goto(articleUrl);
44+
45+
// 日付要素が存在することを確認
46+
const dateElement = page.locator('[data-testid="article-date"]').or(
47+
page.locator('time')
48+
).or(
49+
page.locator('*').filter({ hasText: /\d{4}[-/]\d{1,2}[-/]\d{1,2}/ }).first()
50+
);
51+
52+
// 日付が存在する場合のみテスト
53+
const dateCount = await dateElement.count();
54+
if (dateCount > 0) {
55+
await expect(dateElement.first()).toBeVisible();
56+
}
57+
});
58+
59+
test('記事の本文が表示される', async ({ page }) => {
60+
await page.goto(articleUrl);
61+
62+
// 記事本文のコンテンツが存在することを確認
63+
const content = page.locator('div').filter({ hasText: /\w+/ }).first();
64+
65+
await expect(content).toBeVisible();
66+
});
67+
68+
test('ホームページに戻るナビゲーションが機能する', async ({ page }) => {
69+
await page.goto(articleUrl);
70+
71+
// ホームページリンクまたはロゴをクリック
72+
const homeLink = page.locator('a[href="/"]').or(
73+
page.locator('[data-testid="home-link"]')
74+
).first();
75+
76+
// ホームリンクが存在する場合のみテスト
77+
const homeLinkCount = await homeLink.count();
78+
if (homeLinkCount > 0) {
79+
await homeLink.click();
80+
await expect(page).toHaveURL('/');
81+
}
82+
});
83+
});

e2e/articles-list.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('記事一覧ページ', () => {
4+
test('記事一覧ページが正しく表示される', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// ページタイトルが存在することを確認
8+
await expect(page).toHaveTitle(/Blog/);
9+
10+
// 記事リストが表示されることを確認
11+
const articleList = page.locator('[data-testid="article-list"]').or(
12+
page.locator('article').first()
13+
).or(
14+
page.locator('a[href*="/articles/"]').first()
15+
);
16+
17+
// 記事が存在するか確認
18+
await expect(articleList).toBeVisible();
19+
});
20+
21+
test('記事リンクをクリックして詳細ページに遷移できる', async ({ page }) => {
22+
await page.goto('/');
23+
24+
// 最初の記事リンクを取得してクリック
25+
const firstArticleLink = page.locator('a[href*="/articles/"]').first();
26+
await expect(firstArticleLink).toBeVisible();
27+
28+
const href = await firstArticleLink.getAttribute('href');
29+
await firstArticleLink.click();
30+
31+
// 記事詳細ページに遷移したことを確認
32+
await expect(page).toHaveURL(new RegExp(href!));
33+
});
34+
35+
test('ページネーションが表示される(記事が多い場合)', async ({ page }) => {
36+
await page.goto('/');
37+
38+
// ページネーションが存在するかチェック(存在しない場合はスキップ)
39+
const pagination = page.locator('[data-testid="pagination"]').or(
40+
page.locator('nav').or(
41+
page.locator('a[href*="/p/"]')
42+
)
43+
);
44+
45+
const paginationCount = await pagination.count();
46+
if (paginationCount > 0) {
47+
await expect(pagination.first()).toBeVisible();
48+
}
49+
});
50+
});

package-lock.json

Lines changed: 66 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint",
10-
"format": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,json,css}'"
10+
"format": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx,json,css}'",
11+
"test:e2e": "playwright test",
12+
"test:e2e:ui": "playwright test --ui"
1113
},
1214
"prettier": {
1315
"trailingComma": "all",
@@ -37,5 +39,8 @@
3739
"react": "^19.0.0",
3840
"react-dom": "^19.0.0",
3941
"typescript": "^5.8.2"
42+
},
43+
"devDependencies": {
44+
"@playwright/test": "^1.54.2"
4045
}
4146
}

playwright.config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './e2e',
5+
fullyParallel: true,
6+
forbidOnly: !!process.env.CI,
7+
retries: process.env.CI ? 2 : 0,
8+
workers: process.env.CI ? 1 : undefined,
9+
reporter: 'html',
10+
use: {
11+
baseURL: 'http://localhost:3000',
12+
trace: 'on-first-retry',
13+
},
14+
15+
projects: [
16+
{
17+
name: 'chromium',
18+
use: { ...devices['Desktop Chrome'] },
19+
},
20+
],
21+
22+
webServer: {
23+
command: 'npm run dev',
24+
url: 'http://localhost:3000',
25+
reuseExistingServer: !process.env.CI,
26+
},
27+
});

0 commit comments

Comments
 (0)