Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
151 changes: 147 additions & 4 deletions e2e/helper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,155 @@
import { Page } from '@playwright/test';
// テスト用の設定

export const TEST_URL = 'http://localhost:3000/e2e';
export const LOAD_TIMEOUT = 5000;

// ヘルパー関数
/** 地図コンテナと canvas の出現を待つ。既存テスト互換。 */
export async function waitForMapLoad(page: Page, selector = '.geolonia') {
// 地図コンテナの存在を確認
await page.waitForSelector(selector);
// Maplibreのcanvasが表示されるまで待機
await page.waitForSelector(`${selector} canvas`, { timeout: LOAD_TIMEOUT });
}

/** Maplibre Map の load + isStyleLoaded を待つ。スタイル依存テスト向け。 */
export async function waitForStyleLoad(page: Page, selector = '#map') {
await page.waitForFunction(
(sel) => {
const el = document.querySelector(sel) as any;
return !!(el && el.geoloniaMap && el.geoloniaMap.loaded() && el.geoloniaMap.isStyleLoaded());
},
selector,
{ timeout: LOAD_TIMEOUT },
);
}

/** container.geoloniaMap が生成済みか確認。 */
export async function hasMapInstance(page: Page, selector = '#map'): Promise<boolean> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
return !!(el && el.geoloniaMap);
}, selector);
}

export async function getCenter(page: Page, selector = '#map'): Promise<{ lat: number; lng: number }> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
const c = el.geoloniaMap.getCenter();
return { lat: c.lat, lng: c.lng };
}, selector);
}

export async function getZoom(page: Page, selector = '#map'): Promise<number> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
return el.geoloniaMap.getZoom();
}, selector);
}

export async function getBearing(page: Page, selector = '#map'): Promise<number> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
return el.geoloniaMap.getBearing();
}, selector);
}

export async function getPitch(page: Page, selector = '#map'): Promise<number> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
return el.geoloniaMap.getPitch();
}, selector);
}

export async function getStyleLayerIds(page: Page, selector = '#map'): Promise<string[]> {
return page.evaluate((sel) => {
const el = document.querySelector(sel) as any;
return el.geoloniaMap.getStyle().layers.map((l: any) => l.id);
}, selector);
}

export async function hasLayer(page: Page, layerId: string, selector = '#map'): Promise<boolean> {
return page.evaluate(
({ sel, id }) => {
const el = document.querySelector(sel) as any;
return !!el.geoloniaMap.getLayer(id);
},
{ sel: selector, id: layerId },
);
}

export async function hasSource(page: Page, sourceId: string, selector = '#map'): Promise<boolean> {
return page.evaluate(
({ sel, id }) => {
const el = document.querySelector(sel) as any;
return !!el.geoloniaMap.getSource(id);
},
{ sel: selector, id: sourceId },
);
}

/**
* Geolonia のスタイル/タイル/スプライト等を最小レスポンスでモックする。
* テスト先頭の beforeEach で呼ぶ前提。
*
* 返すスタイルは layers=[background] のみのミニマル style v8。
* 認証URL検証など transformRequest の挙動は実リクエスト前にここで握る。
*/
export async function mockGeoloniaTiles(page: Page) {
// スタイル JSON (cdn.geolonia.com/style/...)
await page.route(/cdn\.geolonia\.com\/style\/.+\.json/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
version: 8,
sources: {},
layers: [
{ id: 'background', type: 'background', paint: { 'background-color': '#cccccc' } },
],
}),
}),
);

// スプライト / グリフ / フォント関連は 404 でよい (background のみなので参照されない)
await page.route(/cdn\.geolonia\.com\/(sprite|glyph|font)/, (route) =>
route.fulfill({ status: 404, body: '' }),
);

// TileJSON
await page.route(/tileserver\.geolonia\.com\/.*/, (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
tilejson: '2.2.0',
tiles: ['https://tiles.geolonia.com/{z}/{x}/{y}.pbf'],
minzoom: 0,
maxzoom: 14,
}),
}),
);

// ベクトルタイル
await page.route(/\.tiles\.geolonia\.com\/.*\.pbf/, (route) =>
route.fulfill({ status: 204, body: '' }),
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// api.geolonia.com (legacy / permission check 等)
await page.route(/api\.geolonia\.com\/.*/, (route) =>
route.fulfill({ status: 200, contentType: 'application/json', body: '{}' }),
);
}

/**
* 指定 predicate にマッチしたリクエスト URL を記録する。
* 呼び出し時点で配列を返し、以降の page 操作中に push される。
* 認証パラメータ注入の検証などに使用。
*/
export function recordRequests(
page: Page,
predicate: (url: string) => boolean = () => true,
): string[] {
const urls: string[] = [];
page.on('request', (req) => {
if (predicate(req.url())) urls.push(req.url());
});
return urls;
}
35 changes: 35 additions & 0 deletions e2e/sample.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* PR0 sanity-check: helper.ts のヘルパー群が機能していることを確認する最小テスト。
* 後続フェーズで具体的なテストが充実したら削除してよい。
*/
import { test, expect } from '@playwright/test';
import {
TEST_URL,
mockGeoloniaTiles,
waitForStyleLoad,
hasMapInstance,
getStyleLayerIds,
recordRequests,
} from './helper';

test.describe('PR0 helper sanity', () => {
test('mockGeoloniaTiles を適用すると minimal style だけが読み込まれる', async ({ page }) => {
await mockGeoloniaTiles(page);
await page.goto(`${TEST_URL}/nocontrol.html`);
await waitForStyleLoad(page);

expect(await hasMapInstance(page)).toBe(true);
const layers = await getStyleLayerIds(page);
expect(layers).toEqual(['background']);
});

test('recordRequests で geolonia 系へのリクエストを観測できる', async ({ page }) => {
await mockGeoloniaTiles(page);
const styleRequests = recordRequests(page, (url) => url.includes('cdn.geolonia.com/style/'));
await page.goto(`${TEST_URL}/nocontrol.html`);
await waitForStyleLoad(page);

expect(styleRequests.length).toBeGreaterThan(0);
expect(styleRequests[0]).toMatch(/cdn\.geolonia\.com\/style\/.+\.json/);
});
});
36 changes: 5 additions & 31 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,16 @@ export default defineConfig({
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
/*
* embed は maplibre-gl の薄いラッパーであり、クロスブラウザ互換は
* maplibre 本体のテストに委ねる。embed 側の E2E は chromium のみで
* 回し、iteration 速度を優先する。
*/
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
Expand Down
Loading