-
-
Notifications
You must be signed in to change notification settings - Fork 381
[Map] Add E2E tests, close #3022 #3038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { expect, test } from '@playwright/test'; | ||
|
||
test('Can render basic map', async ({ page }) => { | ||
await page.goto('/ux-map/basic?renderer=google'); | ||
|
||
await expect(await page.getByTestId('map')).toBeVisible(); | ||
|
||
// Since we can't test Google Maps rendering due to API costs, we only assert that Google Maps API is (wrongly) loaded | ||
await expect(await page.getByTestId('map')).toContainText('Oops! Something went wrong.'); | ||
await expect(await page.getByTestId('map')).toContainText( | ||
"This page didn't load Google Maps correctly. See the JavaScript console for technical details." | ||
); | ||
}); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { expect, type Page, test } from '@playwright/test'; | ||
import { getSymfonyKernelVersionId } from '../../../../../../../../test/playwright-helpers'; | ||
|
||
async function expectMapToBeVisible(page: Page) { | ||
await expect(await page.getByTestId('map')).toBeVisible(); | ||
await expect(await page.getByTestId('map').locator('.leaflet-pane').first()).toBeAttached(); | ||
await expect(await page.getByTestId('map').locator('.leaflet-control-container')).toBeAttached(); | ||
} | ||
|
||
async function expectOneInfoWindowToBeOpenedAndContainText(page: Page, text: string) { | ||
const popups = page.locator('.leaflet-popup'); | ||
await expect(popups).toHaveCount(1); | ||
await expect(popups.first()).toBeInViewport(); | ||
await expect(popups.first()).toContainText(text); | ||
} | ||
|
||
test('Can render basic map', async ({ page }) => { | ||
await page.goto('/ux-map/basic?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
}); | ||
|
||
test('Can render markers and fit bounds to marker', async ({ page }) => { | ||
await page.goto('/ux-map/with-markers-and-fit-bounds-to-markers?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const markers = page.getByTestId('map').locator('.leaflet-marker-icon'); | ||
await expect(markers, '2 markers should be present').toHaveCount(2); | ||
for (const marker of await markers.all()) { | ||
await expect(marker).toBeInViewport(); | ||
} | ||
|
||
await expect(markers.nth(0)).toHaveAttribute('title', 'Paris'); | ||
await expect(markers.nth(1)).toHaveAttribute('title', 'Lyon'); | ||
}); | ||
|
||
test('Can render markers, zoomed on Paris, Lyon marker should be hidden', async ({ page }) => { | ||
await page.goto('/ux-map/with-markers-and-zoomed-on-paris?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
// Ensure the two markers are rendered, but only the Paris marker should be visible in the viewport | ||
const markers = page.getByTestId('map').locator('.leaflet-marker-icon'); | ||
await expect(markers).toHaveCount(2); | ||
await expect(markers.nth(0)).toHaveAttribute('title', 'Paris'); | ||
await expect(markers.nth(0)).toBeInViewport(); | ||
await expect(markers.nth(1)).toHaveAttribute('title', 'Lyon'); | ||
await expect(markers.nth(1), 'The "Lyon" marker should not be visible').not.toBeInViewport(); | ||
}); | ||
|
||
test('Can render markers and info windows', async ({ page }) => { | ||
await page.goto('/ux-map/with-markers-and-info-windows?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const markers = page.getByTestId('map').locator('.leaflet-marker-icon'); | ||
await expect(markers, '2 markers should be present').toHaveCount(2); | ||
for (const marker of await markers.all()) { | ||
await expect(marker).toBeInViewport(); | ||
} | ||
|
||
await expect(markers.nth(0)).toHaveAttribute('title', 'Paris'); | ||
await expect(markers.nth(1)).toHaveAttribute('title', 'Lyon'); | ||
|
||
// Ensure only one popup is visible at a time, the popup for Paris should be opened by default | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'Capital of France'); | ||
|
||
// Click on the Lyon marker to open its popup, the Paris popup should close | ||
await markers.nth(1).click(); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'Famous for its gastronomy'); | ||
}); | ||
|
||
test('Can render markers with custom icons', async ({ page }) => { | ||
await page.goto('/ux-map/with-markers-and-custom-icons?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const markers = page.getByTestId('map').locator('.leaflet-marker-icon'); | ||
await expect(markers, '3 markers should be present').toHaveCount(3); | ||
for (const marker of await markers.all()) { | ||
await expect(marker).toBeInViewport(); | ||
} | ||
|
||
await expect(markers.nth(0)).toHaveAttribute('title', 'Paris'); | ||
expect(await markers.nth(0).innerHTML()).toEqual( | ||
'<svg viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path fill="currentColor" d="M8.21 17c.44-.85.85-1.84 1.23-3H9v-2h1c.61-2.6 1-5.87 1-10h2c0 4.13.4 7.4 1 10h1v2h-.44c.38 1.16.79 2.15 1.23 3H17v2l2 3h-2.42c-.77-1.76-2.53-3-4.58-3s-3.81 1.24-4.58 3H5l2-3l-.03-2zm4.38-3h-1.18a22 22 0 0 1-1.13 3h3.44c-.4-.87-.79-1.87-1.13-3"></path></svg>' | ||
); | ||
|
||
await expect(markers.nth(1)).toHaveAttribute('title', 'Lyon'); | ||
expect(await markers.nth(1).innerHTML()).toEqual( | ||
'<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4"></path></svg>' | ||
); | ||
|
||
await expect(markers.nth(2)).toHaveAttribute('title', 'Bordeaux'); | ||
await expect(markers.nth(2)).toHaveAttribute( | ||
'src', | ||
getSymfonyKernelVersionId() >= 70200 | ||
? '/assets/icons/mdi/glass-wine-SOLVwOG.svg' | ||
: '/assets/icons/mdi/glass-wine-48e2d5c0e18f9b07dab82e113ec7490e.svg' | ||
); | ||
}); | ||
|
||
test('Can render polygons', async ({ page }) => { | ||
await page.goto('/ux-map/with-polygons?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const paths = page.getByTestId('map').locator('path.leaflet-interactive'); | ||
await expect(paths, '2 polygons must be present').toHaveCount(2); | ||
await expect(paths.nth(0)).toHaveAttribute( | ||
'd', | ||
'M548 276L656 188L762 260L708 433L573 384zM615 352L696 354L678 236L640 250z' | ||
); | ||
await expect(paths.nth(1)).toHaveAttribute('d', 'M870 476L795 364L844 395L911 508z'); | ||
|
||
// Workaround for `paths.nth(0).click({ relative: { ... } })` which does not work, it tries to click the center of the polygon, | ||
// but since it's empty, the popup can't be opened. | ||
const firstPathBoundingBox = await paths.nth(0).boundingBox(); | ||
await page.mouse.click(firstPathBoundingBox.x + 40, firstPathBoundingBox.y + 100); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A weird shape on the France'); | ||
|
||
const secondPathBoundingBox = await paths.nth(1).boundingBox(); | ||
await page.mouse.click(secondPathBoundingBox.x + 50, secondPathBoundingBox.y + 40); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A polygon covering some of the main cities in Italy'); | ||
}); | ||
|
||
test('Can render polylines', async ({ page }) => { | ||
await page.goto('/ux-map/with-polylines?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const paths = page.getByTestId('map').locator('path.leaflet-interactive'); | ||
await expect(paths, '2 polylines must be present').toHaveCount(2); | ||
await expect(paths.nth(0)).toHaveAttribute('d', 'M640 250L696 354L708 433L573 384'); | ||
await expect(paths.nth(1)).toHaveAttribute('d', 'M548 276L551 306L603 302'); | ||
|
||
// Workaround for `paths.nth(0).click({ relative: { ... } })` which does not work, it tries to click the center of the polygon, | ||
// but since it's empty, the popup can't be opened. | ||
const firstPathBoundingBox = await paths.nth(0).boundingBox(); | ||
await page.mouse.click(firstPathBoundingBox.x + 95, firstPathBoundingBox.y + 50); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A line passing through Paris, Lyon, Marseille, Bordeaux'); | ||
|
||
const secondPathBoundingBox = await paths.nth(1).boundingBox(); | ||
await page.mouse.click(secondPathBoundingBox.x + 5, secondPathBoundingBox.y + 25); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A line passing through Rennes, Nantes and Tours'); | ||
}); | ||
|
||
test('Can render circles', async ({ page }) => { | ||
await page.goto('/ux-map/with-circles?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const paths = page.getByTestId('map').locator('path.leaflet-interactive'); | ||
await expect(paths, '2 circles must be present').toHaveCount(2); | ||
await expect(paths.nth(0)).toHaveAttribute( | ||
'd', | ||
'M623.5256177777774,250.21082480695986a16,16 0 1,0 32,0 a16,16 0 1,0 -32,0 ' | ||
); | ||
await expect(paths.nth(1)).toHaveAttribute( | ||
'd', | ||
'M687.0390399999997,354.0936387274919a9,9 0 1,0 18,0 a9,9 0 1,0 -18,0 ' | ||
); | ||
|
||
await paths.nth(0).click(); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A 50km radius circle centered on Paris'); | ||
|
||
await paths.nth(1).click(); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A 30km radius circle centered on Lyon'); | ||
}); | ||
|
||
test('Can render rectangles', async ({ page }) => { | ||
await page.goto('/ux-map/with-rectangles?renderer=leaflet'); | ||
await expectMapToBeVisible(page); | ||
|
||
const paths = page.getByTestId('map').locator('path.leaflet-interactive'); | ||
await expect(paths, '2 rectangles must be present').toHaveCount(2); | ||
await expect(paths.nth(0)).toHaveAttribute('d', 'M640 250L640 188L656 188L656 250z'); | ||
await expect(paths.nth(1)).toHaveAttribute('d', 'M573 384L573 354L696 354L696 384z'); | ||
|
||
await paths.nth(0).click(); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A rectangle from Paris to Lille'); | ||
|
||
await paths.nth(1).click(); | ||
await expectOneInfoWindowToBeOpenedAndContainText(page, 'A rectangle from Bordeaux to Lyon'); | ||
}); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import * as path from 'node:path'; | ||
import * as fs from 'fs'; | ||
|
||
export function getSymfonyKernelVersionId(): number { | ||
const kernelPath = path.join(import.meta.dirname, '../test_apps/e2e-app/vendor/symfony/http-kernel/Kernel.php'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would use an ENV_VAR here (as its done for KERNEL_CLASS in a way) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about it too but re-using
The |
||
if (!fs.existsSync(kernelPath)) { | ||
throw new Error(`Unable to read Symfony Kernel version ID, the file "${kernelPath}" does not exist.`) | ||
} | ||
|
||
const match = fs.readFileSync(kernelPath, 'utf8').match((/VERSION_ID = (\d+)/)); | ||
if (match === null) { | ||
throw new Error(`Unable to extract Symfony Kernel version ID.`) | ||
} | ||
|
||
return Number(match[1]); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
ux_map: | ||
# https://symfony.com/bundles/ux-map/current/index.html#available-renderers | ||
renderer: '%env(resolve:default::UX_MAP_DSN)%' | ||
renderer: '' # configured in Kernel.php | ||
google_maps: | ||
# define the default map id for all maps (https://developers.google.com/maps/documentation/get-map-id) | ||
default_map_id: null | ||
default_map_id: '%env(resolve:default::GOOGLE_MAPS_DEFAULT_MAP_ID)%' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,6 +85,9 @@ | |
'react-dom' => [ | ||
'version' => '18.3.1', | ||
], | ||
'react-dom/client' => [ | ||
'version' => '18.3.1', | ||
], | ||
Comment on lines
+88
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to #3030 |
||
'scheduler' => [ | ||
'version' => '0.23.2', | ||
], | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not do this test.
i think this could be a (tiny but still very real) problem in term of privacy, as we are making requests on behalf of the user... (even if it's on its own local machine)..
If someone want Google Maps fully tested, they probably already pay for GoogleMaps .....
so maybe they can invest some coins around here to support the work....
In the meantime I clearly would not spend too much time or energy to test a (very) costly (and "not-always" privacy-compliant) solution your our own time and/or money 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I see what you mean, but since #2146 Map Bridges tests were run inside a browser and so requests were already made to Google.
I think that's fine if we make it opt-out explicitly:
pnpm run test:browser
: run browsers tests on all UX packages withtest:browser
pnpm script except UX Google Mappnpm run test:browser:no-privacy
(or something similar): run browsers tests on all UX packages withtest:browser
pnpm script including UX Google MapAbout writing real tests for Google Map Bridge too, I thought about putting my own API key as a GitHub secret (so no one can see it), but if someone decide to trigger 10k times the CI, I'm bankrupt 🤯
Running a test that ensure the Google Maps API is loaded is the best of both worlds imo