Skip to content

Commit 4620423

Browse files
authored
Merge pull request #489 from niryuu/411/e2e-foundation
test: e2e foundation (chromium限定化 + helper関数拡充)
2 parents 1423e5b + 8cc3ace commit 4620423

3 files changed

Lines changed: 196 additions & 35 deletions

File tree

e2e/helper.ts

Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,164 @@
11
import { Page } from '@playwright/test';
2-
// テスト用の設定
2+
33
export const TEST_URL = 'http://localhost:3000/e2e';
44
export const LOAD_TIMEOUT = 5000;
55

6-
// ヘルパー関数
6+
/** 地図コンテナと canvas の出現を待つ。既存テスト互換。 */
77
export async function waitForMapLoad(page: Page, selector = '.geolonia') {
8-
// 地図コンテナの存在を確認
98
await page.waitForSelector(selector);
10-
// Maplibreのcanvasが表示されるまで待機
119
await page.waitForSelector(`${selector} canvas`, { timeout: LOAD_TIMEOUT });
1210
}
11+
12+
/** Maplibre Map の load + isStyleLoaded を待つ。スタイル依存テスト向け。 */
13+
export async function waitForStyleLoad(page: Page, selector = '#map') {
14+
await page.waitForFunction(
15+
(sel) => {
16+
const el = document.querySelector(sel) as any;
17+
return !!(el && el.geoloniaMap && el.geoloniaMap.loaded() && el.geoloniaMap.isStyleLoaded());
18+
},
19+
selector,
20+
{ timeout: LOAD_TIMEOUT },
21+
);
22+
}
23+
24+
/** container.geoloniaMap が生成済みか確認。 */
25+
export async function hasMapInstance(page: Page, selector = '#map'): Promise<boolean> {
26+
return page.evaluate((sel) => {
27+
const el = document.querySelector(sel) as any;
28+
return !!(el && el.geoloniaMap);
29+
}, selector);
30+
}
31+
32+
export async function getCenter(page: Page, selector = '#map'): Promise<{ lat: number; lng: number }> {
33+
return page.evaluate((sel) => {
34+
const el = document.querySelector(sel) as any;
35+
const c = el.geoloniaMap.getCenter();
36+
return { lat: c.lat, lng: c.lng };
37+
}, selector);
38+
}
39+
40+
export async function getZoom(page: Page, selector = '#map'): Promise<number> {
41+
return page.evaluate((sel) => {
42+
const el = document.querySelector(sel) as any;
43+
return el.geoloniaMap.getZoom();
44+
}, selector);
45+
}
46+
47+
export async function getBearing(page: Page, selector = '#map'): Promise<number> {
48+
return page.evaluate((sel) => {
49+
const el = document.querySelector(sel) as any;
50+
return el.geoloniaMap.getBearing();
51+
}, selector);
52+
}
53+
54+
export async function getPitch(page: Page, selector = '#map'): Promise<number> {
55+
return page.evaluate((sel) => {
56+
const el = document.querySelector(sel) as any;
57+
return el.geoloniaMap.getPitch();
58+
}, selector);
59+
}
60+
61+
export async function getStyleLayerIds(page: Page, selector = '#map'): Promise<string[]> {
62+
return page.evaluate((sel) => {
63+
const el = document.querySelector(sel) as any;
64+
return el.geoloniaMap.getStyle().layers.map((l: any) => l.id);
65+
}, selector);
66+
}
67+
68+
export async function hasLayer(page: Page, layerId: string, selector = '#map'): Promise<boolean> {
69+
return page.evaluate(
70+
({ sel, id }) => {
71+
const el = document.querySelector(sel) as any;
72+
return !!el.geoloniaMap.getLayer(id);
73+
},
74+
{ sel: selector, id: layerId },
75+
);
76+
}
77+
78+
export async function hasSource(page: Page, sourceId: string, selector = '#map'): Promise<boolean> {
79+
return page.evaluate(
80+
({ sel, id }) => {
81+
const el = document.querySelector(sel) as any;
82+
return !!el.geoloniaMap.getSource(id);
83+
},
84+
{ sel: selector, id: sourceId },
85+
);
86+
}
87+
88+
// Geolonia ベクトルタイルのホスト。TileJSON で返す URL とリクエスト捕捉用 regex の両方が
89+
// この定数から派生するので、両者が乖離することは構造上ありえない。
90+
const GEOLONIA_TILE_HOST = 'tiles.geolonia.com';
91+
const GEOLONIA_TILE_URL_TEMPLATE = `https://${GEOLONIA_TILE_HOST}/{z}/{x}/{y}.pbf`;
92+
// サブドメイン付き (`osm.v3.tiles.geolonia.com` 等) もサブドメイン無しも同一 regex で捕捉。
93+
const GEOLONIA_TILE_PBF_REGEX = new RegExp(
94+
String.raw`(?:[^/]+\.)?` + GEOLONIA_TILE_HOST.replace(/\./g, String.raw`\.`) + String.raw`\/.*\.pbf`,
95+
);
96+
97+
/**
98+
* Geolonia のスタイル/タイル/スプライト等を最小レスポンスでモックする。
99+
* テスト先頭の beforeEach で呼ぶ前提。
100+
*
101+
* 返すスタイルは layers=[background] のみのミニマル style v8。
102+
* 認証URL検証など transformRequest の挙動は実リクエスト前にここで握る。
103+
*/
104+
export async function mockGeoloniaTiles(page: Page) {
105+
// スタイル JSON (cdn.geolonia.com/style/...)
106+
await page.route(/cdn\.geolonia\.com\/style\/.+\.json/, (route) =>
107+
route.fulfill({
108+
status: 200,
109+
contentType: 'application/json',
110+
body: JSON.stringify({
111+
version: 8,
112+
sources: {},
113+
layers: [
114+
{ id: 'background', type: 'background', paint: { 'background-color': '#cccccc' } },
115+
],
116+
}),
117+
}),
118+
);
119+
120+
// スプライト / グリフ / フォント関連は 404 でよい (background のみなので参照されない)
121+
await page.route(/cdn\.geolonia\.com\/(sprite|glyph|font)/, (route) =>
122+
route.fulfill({ status: 404, body: '' }),
123+
);
124+
125+
// TileJSON
126+
await page.route(/tileserver\.geolonia\.com\/.*/, (route) =>
127+
route.fulfill({
128+
status: 200,
129+
contentType: 'application/json',
130+
body: JSON.stringify({
131+
tilejson: '2.2.0',
132+
tiles: [GEOLONIA_TILE_URL_TEMPLATE],
133+
minzoom: 0,
134+
maxzoom: 14,
135+
}),
136+
}),
137+
);
138+
139+
// ベクトルタイル (上記 TileJSON が返した URL を必ず捕捉)
140+
await page.route(GEOLONIA_TILE_PBF_REGEX, (route) =>
141+
route.fulfill({ status: 204, body: '' }),
142+
);
143+
144+
// api.geolonia.com (legacy / permission check 等)
145+
await page.route(/api\.geolonia\.com\/.*/, (route) =>
146+
route.fulfill({ status: 200, contentType: 'application/json', body: '{}' }),
147+
);
148+
}
149+
150+
/**
151+
* 指定 predicate にマッチしたリクエスト URL を記録する。
152+
* 呼び出し時点で配列を返し、以降の page 操作中に push される。
153+
* 認証パラメータ注入の検証などに使用。
154+
*/
155+
export function recordRequests(
156+
page: Page,
157+
predicate: (url: string) => boolean = () => true,
158+
): string[] {
159+
const urls: string[] = [];
160+
page.on('request', (req) => {
161+
if (predicate(req.url())) urls.push(req.url());
162+
});
163+
return urls;
164+
}

e2e/sample.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* PR0 sanity-check: helper.ts のヘルパー群が機能していることを確認する最小テスト。
3+
* 後続フェーズで具体的なテストが充実したら削除してよい。
4+
*/
5+
import { test, expect } from '@playwright/test';
6+
import {
7+
TEST_URL,
8+
mockGeoloniaTiles,
9+
waitForStyleLoad,
10+
hasMapInstance,
11+
getStyleLayerIds,
12+
recordRequests,
13+
} from './helper';
14+
15+
test.describe('PR0 helper sanity', () => {
16+
test('mockGeoloniaTiles を適用すると minimal style だけが読み込まれる', async ({ page }) => {
17+
await mockGeoloniaTiles(page);
18+
await page.goto(`${TEST_URL}/nocontrol.html`);
19+
await waitForStyleLoad(page);
20+
21+
expect(await hasMapInstance(page)).toBe(true);
22+
const layers = await getStyleLayerIds(page);
23+
expect(layers).toEqual(['background']);
24+
});
25+
26+
test('recordRequests で geolonia 系へのリクエストを観測できる', async ({ page }) => {
27+
await mockGeoloniaTiles(page);
28+
const styleRequests = recordRequests(page, (url) => url.includes('cdn.geolonia.com/style/'));
29+
await page.goto(`${TEST_URL}/nocontrol.html`);
30+
await waitForStyleLoad(page);
31+
32+
expect(styleRequests.length).toBeGreaterThan(0);
33+
expect(styleRequests[0]).toMatch(/cdn\.geolonia\.com\/style\/.+\.json/);
34+
});
35+
});

playwright.config.ts

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,42 +32,16 @@ export default defineConfig({
3232
trace: 'on-first-retry',
3333
},
3434

35-
/* Configure projects for major browsers */
35+
/*
36+
* embed は maplibre-gl の薄いラッパーであり、クロスブラウザ互換は
37+
* maplibre 本体のテストに委ねる。embed 側の E2E は chromium のみで
38+
* 回し、iteration 速度を優先する。
39+
*/
3640
projects: [
3741
{
3842
name: 'chromium',
3943
use: { ...devices['Desktop Chrome'] },
4044
},
41-
42-
{
43-
name: 'firefox',
44-
use: { ...devices['Desktop Firefox'] },
45-
},
46-
47-
{
48-
name: 'webkit',
49-
use: { ...devices['Desktop Safari'] },
50-
},
51-
52-
/* Test against mobile viewports. */
53-
// {
54-
// name: 'Mobile Chrome',
55-
// use: { ...devices['Pixel 5'] },
56-
// },
57-
// {
58-
// name: 'Mobile Safari',
59-
// use: { ...devices['iPhone 12'] },
60-
// },
61-
62-
/* Test against branded browsers. */
63-
// {
64-
// name: 'Microsoft Edge',
65-
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
66-
// },
67-
// {
68-
// name: 'Google Chrome',
69-
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
70-
// },
7145
],
7246

7347
/* Run your local dev server before starting the tests */

0 commit comments

Comments
 (0)