diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000..0b87512d --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,25 @@ +name: Playwright Tests +on: + pull_request: + branches: [ master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: playwright install --with-deps + - name: Run Playwright tests + run: playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore index fded70c1..c730ec13 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ */node_modules /dist /coverage +/playwright-report +/test-results \ No newline at end of file diff --git a/e2e/layers.spec.ts b/e2e/layers.spec.ts new file mode 100644 index 00000000..a9fa0901 --- /dev/null +++ b/e2e/layers.spec.ts @@ -0,0 +1,267 @@ +import { test, expect } from '@playwright/test' + +test.describe('map layers', () => { + test.beforeEach(async ({ context, page }) => { + // Clear all storage to ensure clean state + await context.clearCookies() + + await page.goto('/') + await page.evaluate(() => { + localStorage.clear() + sessionStorage.clear() + }) + + // Navigate with seed parameter + await page.goto('/?seed=test-seed') + + const mapElement = page.locator('#map') + await expect(mapElement).toBeVisible() + + // Wait for map generation to complete + await expect(mapElement.locator('#terrs')).toBeAttached({ timeout: 30000 }) + await expect(mapElement.locator('#labels')).toBeAttached() + await expect(page.locator('#loading')).toBeHidden({ timeout: 30000 }) + await page.waitForTimeout(1000) + }) + + // Ocean and water layers + test('ocean layer', async ({ page }) => { + const ocean = page.locator('#ocean') + await expect(ocean).toBeAttached() + const html = await ocean.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('ocean.html') + }) + + test('lakes layer', async ({ page }) => { + const lakes = page.locator('#lakes') + await expect(lakes).toBeAttached() + const html = await lakes.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('lakes.html') + }) + + test('coastline layer', async ({ page }) => { + const coastline = page.locator('#coastline') + await expect(coastline).toBeAttached() + const html = await coastline.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('coastline.html') + }) + + // Terrain and heightmap layers + test('terrain layer', async ({ page }) => { + const terrs = page.locator('#terrs') + await expect(terrs).toBeAttached() + const html = await terrs.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('terrain.html') + }) + + test('landmass layer', async ({ page }) => { + const landmass = page.locator('#landmass') + await expect(landmass).toBeAttached() + const html = await landmass.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('landmass.html') + }) + + // Climate and environment layers + test('biomes layer', async ({ page }) => { + const biomes = page.locator('#biomes') + await expect(biomes).toBeAttached() + const html = await biomes.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('biomes.html') + }) + + test('ice layer', async ({ page }) => { + const ice = page.locator('#ice') + await expect(ice).toBeAttached() + const html = await ice.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('ice.html') + }) + + test('temperature layer', async ({ page }) => { + const temperature = page.locator('#temperature') + await expect(temperature).toBeAttached() + const html = await temperature.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('temperature.html') + }) + + test('precipitation layer', async ({ page }) => { + const prec = page.locator('#prec') + await expect(prec).toBeAttached() + const html = await prec.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('precipitation.html') + }) + + // Geographic features + test('rivers layer', async ({ page }) => { + const rivers = page.locator('#rivers') + await expect(rivers).toBeAttached() + const html = await rivers.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('rivers.html') + }) + + test('relief layer', async ({ page }) => { + const terrain = page.locator('#terrain') + await expect(terrain).toBeAttached() + const html = await terrain.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('relief.html') + }) + + // Political layers + test('states/regions layer', async ({ page }) => { + const regions = page.locator('#regions') + await expect(regions).toBeAttached() + const html = await regions.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('regions.html') + }) + + test('provinces layer', async ({ page }) => { + const provs = page.locator('#provs') + await expect(provs).toBeAttached() + const html = await provs.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('provinces.html') + }) + + test('borders layer', async ({ page }) => { + const borders = page.locator('#borders') + await expect(borders).toBeAttached() + const html = await borders.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('borders.html') + }) + + // Cultural layers + test('cultures layer', async ({ page }) => { + const cults = page.locator('#cults') + await expect(cults).toBeAttached() + const html = await cults.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('cultures.html') + }) + + test('religions layer', async ({ page }) => { + const relig = page.locator('#relig') + await expect(relig).toBeAttached() + const html = await relig.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('religions.html') + }) + + // Infrastructure layers + test('routes layer', async ({ page }) => { + const routes = page.locator('#routes') + await expect(routes).toBeAttached() + const html = await routes.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('routes.html') + }) + + // Settlement layers + test('burgs/icons layer', async ({ page }) => { + const icons = page.locator('#icons') + await expect(icons).toBeAttached() + const html = await icons.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('icons.html') + }) + + test('anchors layer', async ({ page }) => { + const anchors = page.locator('#anchors') + await expect(anchors).toBeAttached() + const html = await anchors.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('anchors.html') + }) + + // Labels layer (without text content due to font rendering) + test('labels layer', async ({ page }) => { + const labels = page.locator('#labels') + await expect(labels).toBeAttached() + // Remove text content but keep structure (text rendering varies) + const html = await labels.evaluate((el) => { + const clone = el.cloneNode(true) as Element + clone.querySelectorAll('text, tspan').forEach((t) => t.remove()) + return clone.outerHTML + }) + expect(html).toMatchSnapshot('labels.html') + }) + + // Military and markers + test('markers layer', async ({ page }) => { + const markers = page.locator('#markers') + await expect(markers).toBeAttached() + const html = await markers.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('markers.html') + }) + + test('armies layer', async ({ page }) => { + const armies = page.locator('#armies') + await expect(armies).toBeAttached() + const html = await armies.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('armies.html') + }) + + // Special features + test('zones layer', async ({ page }) => { + const zones = page.locator('#zones') + await expect(zones).toBeAttached() + const html = await zones.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('zones.html') + }) + + test('emblems layer', async ({ page }) => { + const emblems = page.locator('#emblems') + await expect(emblems).toBeAttached() + const html = await emblems.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('emblems.html') + }) + + // Grid and coordinates + test('cells layer', async ({ page }) => { + const cells = page.locator('g#cells') + await expect(cells).toBeAttached() + const html = await cells.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('cells.html') + }) + + test('coordinates layer', async ({ page }) => { + const coordinates = page.locator('#coordinates') + await expect(coordinates).toBeAttached() + const html = await coordinates.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('coordinates.html') + }) + + test('compass layer', async ({ page }) => { + const compass = page.locator('#compass') + await expect(compass).toBeAttached() + const html = await compass.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('compass.html') + }) + + // UI elements + test('scale bar layer', async ({ page }) => { + const scaleBar = page.locator('#scaleBar') + await expect(scaleBar).toBeAttached() + // Scale bar has randomized distances, snapshot structure only + const html = await scaleBar.evaluate((el) => { + const clone = el.cloneNode(true) as Element + clone.querySelectorAll('text').forEach((t) => t.remove()) + return clone.outerHTML + }) + expect(html).toMatchSnapshot('scaleBar.html') + }) + + test('ruler layer', async ({ page }) => { + const ruler = page.locator('#ruler') + await expect(ruler).toBeAttached() + const html = await ruler.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('ruler.html') + }) + + test('vignette layer', async ({ page }) => { + const vignette = page.locator('#vignette') + await expect(vignette).toBeAttached() + const html = await vignette.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('vignette.html') + }) + + // Population layer + test('population layer', async ({ page }) => { + const population = page.locator('#population') + await expect(population).toBeAttached() + const html = await population.evaluate((el) => el.outerHTML) + expect(html).toMatchSnapshot('population.html') + }) +}) diff --git a/e2e/layers.spec.ts-snapshots/anchors-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/anchors-chromium-darwin.html new file mode 100644 index 00000000..7c2df822 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/anchors-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/armies-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/armies-chromium-darwin.html new file mode 100644 index 00000000..face6396 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/armies-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/biomes-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/biomes-chromium-darwin.html new file mode 100644 index 00000000..582a9c1d --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/biomes-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/borders-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/borders-chromium-darwin.html new file mode 100644 index 00000000..92b98dc8 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/borders-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/cells-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/cells-chromium-darwin.html new file mode 100644 index 00000000..d73d9b2f --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/cells-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/coastline-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/coastline-chromium-darwin.html new file mode 100644 index 00000000..7a2c4c51 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/coastline-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/compass-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/compass-chromium-darwin.html new file mode 100644 index 00000000..3c0892a6 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/compass-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/coordinates-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/coordinates-chromium-darwin.html new file mode 100644 index 00000000..48e6c40b --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/coordinates-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/cultures-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/cultures-chromium-darwin.html new file mode 100644 index 00000000..193726a3 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/cultures-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/emblems-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/emblems-chromium-darwin.html new file mode 100644 index 00000000..1de7ef9d --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/emblems-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/ice-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/ice-chromium-darwin.html new file mode 100644 index 00000000..72ac77e8 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/ice-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/icons-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/icons-chromium-darwin.html new file mode 100644 index 00000000..629db2b0 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/icons-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/labels-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/labels-chromium-darwin.html new file mode 100644 index 00000000..6ffcf3b9 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/labels-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/lakes-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/lakes-chromium-darwin.html new file mode 100644 index 00000000..cce3f70e --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/lakes-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/landmass-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/landmass-chromium-darwin.html new file mode 100644 index 00000000..ec70a34e --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/landmass-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/markers-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/markers-chromium-darwin.html new file mode 100644 index 00000000..100a1e3f --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/markers-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/ocean-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/ocean-chromium-darwin.html new file mode 100644 index 00000000..b950e1a7 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/ocean-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/population-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/population-chromium-darwin.html new file mode 100644 index 00000000..10175492 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/population-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/precipitation-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/precipitation-chromium-darwin.html new file mode 100644 index 00000000..8ab517cb --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/precipitation-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/provinces-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/provinces-chromium-darwin.html new file mode 100644 index 00000000..3fe87d6e --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/provinces-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/regions-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/regions-chromium-darwin.html new file mode 100644 index 00000000..848ce4a3 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/regions-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/relief-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/relief-chromium-darwin.html new file mode 100644 index 00000000..6883fe5b --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/relief-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/religions-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/religions-chromium-darwin.html new file mode 100644 index 00000000..85c96e30 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/religions-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/rivers-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/rivers-chromium-darwin.html new file mode 100644 index 00000000..087b4d8d --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/rivers-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/routes-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/routes-chromium-darwin.html new file mode 100644 index 00000000..6739112c --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/routes-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/ruler-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/ruler-chromium-darwin.html new file mode 100644 index 00000000..755b2d65 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/ruler-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/scaleBar-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/scaleBar-chromium-darwin.html new file mode 100644 index 00000000..9ebec74e --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/scaleBar-chromium-darwin.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/temperature-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/temperature-chromium-darwin.html new file mode 100644 index 00000000..36464dbd --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/temperature-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/terrain-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/terrain-chromium-darwin.html new file mode 100644 index 00000000..bc13f8be --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/terrain-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/vignette-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/vignette-chromium-darwin.html new file mode 100644 index 00000000..6eaf80a4 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/vignette-chromium-darwin.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/e2e/layers.spec.ts-snapshots/zones-chromium-darwin.html b/e2e/layers.spec.ts-snapshots/zones-chromium-darwin.html new file mode 100644 index 00000000..14cd5141 --- /dev/null +++ b/e2e/layers.spec.ts-snapshots/zones-chromium-darwin.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9af3fd78..67512031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,10 @@ "polylabel": "^2.0.1" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@types/d3": "^7.4.3", "@types/delaunator": "^5.0.3", + "@types/node": "^25.0.10", "@types/polylabel": "^1.1.3", "@vitest/browser": "^4.0.18", "@vitest/browser-playwright": "^4.0.18", @@ -478,6 +480,22 @@ "dev": true, "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -1165,6 +1183,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/polylabel": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.1.3.tgz", @@ -1997,6 +2026,7 @@ "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright-core": "1.57.0" }, @@ -2269,6 +2299,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", diff --git a/package.json b/package.json index 1c802071..9d3fbe11 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,14 @@ "build": "tsc && vite build", "preview": "vite preview", "test": "vitest", - "test:browser": "vitest --config=vitest.browser.config.ts" + "test:browser": "vitest --config=vitest.browser.config.ts", + "test:e2e": "playwright test" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@types/d3": "^7.4.3", "@types/delaunator": "^5.0.3", + "@types/node": "^25.0.10", "@types/polylabel": "^1.1.3", "@vitest/browser": "^4.0.18", "@vitest/browser-playwright": "^4.0.18", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..558f6b3f --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [['html', { open: 'always' }]], + use: { + baseURL: 'http://localhost:4173', + trace: 'on-first-retry', + // Fixed viewport to ensure consistent map rendering + viewport: { width: 1280, height: 720 }, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: { + command: 'npm run build && npm run preview', + url: 'http://localhost:4173', + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}) diff --git a/tsconfig.json b/tsconfig.json index 8b583a9d..01672af5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/e2e"] } \ No newline at end of file