diff --git a/core/playwright.config.ts b/core/playwright.config.ts index 677625fed49..52bae646921 100644 --- a/core/playwright.config.ts +++ b/core/playwright.config.ts @@ -46,14 +46,16 @@ const generateProjects = () => { ...project, metadata: { mode, - rtl: false + rtl: false, + _testing: true } }); projectsWithMetadata.push({ ...project, metadata: { mode, - rtl: true + rtl: true, + _testing: true } }); }); @@ -72,7 +74,14 @@ const config: PlaywrightTestConfig = { * Maximum time expect() should wait for the condition to be met. * For example in `await expect(locator).toHaveText();` */ - timeout: 5000 + timeout: 5000, + toMatchSnapshot: { + /** + * Increases the maximum allowed pixel difference to account + * for slight browser rendering inconsistencies. + */ + maxDiffPixelRatio: 0.05 + } }, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, diff --git a/core/src/components/ripple-effect/test/basic/index.html b/core/src/components/ripple-effect/test/basic/index.html index be8b313dcfb..d9016411e73 100644 --- a/core/src/components/ripple-effect/test/basic/index.html +++ b/core/src/components/ripple-effect/test/basic/index.html @@ -46,18 +46,18 @@

- Small + Small

- Large + Large

- Large + Large

- Large + Large

-
+
This is just a div + effect behind Nested button diff --git a/core/src/components/ripple-effect/test/basic/ripple-effect.e2e.ts b/core/src/components/ripple-effect/test/basic/ripple-effect.e2e.ts new file mode 100644 index 00000000000..ddc617e128d --- /dev/null +++ b/core/src/components/ripple-effect/test/basic/ripple-effect.e2e.ts @@ -0,0 +1,63 @@ +import { expect } from '@playwright/test'; +import type { IonicPage } from '@utils/test/playwright'; +import { test } from '@utils/test/playwright'; + +test.describe('ripple-effect: basic', () => { + test('should add .ion-activated when pressed', async ({ page }) => { + await verifyRippleEffect(page, '#small-btn'); + await verifyRippleEffect(page, '#large-btn'); + await verifyRippleEffect(page, '#large-btn-outline'); + await verifyRippleEffect(page, '#large-btn-clear'); + await verifyRippleEffect(page, '.block'); + }); + + test.describe('ripple effect with nested ion-button', () => { + test('should add .ion-activated when the block is pressed', async ({ page }) => { + await page.goto(`/src/components/ripple-effect/test/basic?ionic:_testing=false&ionic:mode=md`); + + const el = page.locator('#ripple-with-button'); + + await el.scrollIntoViewIfNeeded(); + + const boundingBox = await el.boundingBox(); + + if (boundingBox) { + await page.mouse.move(boundingBox.x + 5, boundingBox.y + 5); + await page.mouse.down(); + } + + // Waits for the ripple effect to be added + await page.waitForSelector('.ion-activated'); + + const elHandle = await el.elementHandle(); + const classes = await elHandle?.evaluate((el) => el.classList.value); + expect(classes).toMatch('ion-activated'); + }); + + test('should add .ion-activated when the button is pressed', async ({ page }) => { + await verifyRippleEffect(page, '#ripple-with-button ion-button'); + }); + }); +}); + +const verifyRippleEffect = async (page: IonicPage, selector: string) => { + await page.goto(`/src/components/ripple-effect/test/basic?ionic:_testing=false&ionic:mode=md`); + + const el = page.locator(selector); + + await el.scrollIntoViewIfNeeded(); + + const boundingBox = await el.boundingBox(); + + if (boundingBox) { + await page.mouse.move(boundingBox.x + boundingBox.width / 2, boundingBox.y + boundingBox.height / 2); + await page.mouse.down(); + } + + // Waits for the ripple effect to be added + await page.waitForSelector(`${selector}.ion-activated`); + + const elHandle = await el.elementHandle(); + const classes = await elHandle?.evaluate((el) => el.classList.value); + expect(classes).toMatch('ion-activated'); +}; diff --git a/core/src/utils/tap-click.ts b/core/src/utils/tap-click.ts index 4c486dad0ee..b0c0577d374 100644 --- a/core/src/utils/tap-click.ts +++ b/core/src/utils/tap-click.ts @@ -15,7 +15,8 @@ export const startTapClick = (config: Config) => { const clearDefers = new WeakMap(); const isScrolling = () => { - return scrollingEl?.parentElement !== null; + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain + return scrollingEl !== undefined && scrollingEl.parentElement !== null; }; // Touch Events @@ -169,7 +170,7 @@ const getActivatableTarget = (ev: any): any => { const path = ev.composedPath() as HTMLElement[]; for (let i = 0; i < path.length - 2; i++) { const el = path[i]; - if (el?.classList.contains('ion-activatable')) { + if (!(el instanceof ShadowRoot) && el.classList.contains('ion-activatable')) { return el; } } diff --git a/core/src/utils/test/playwright/fixtures.ts b/core/src/utils/test/playwright/fixtures.ts index 8af41e90b03..208aa554e47 100644 --- a/core/src/utils/test/playwright/fixtures.ts +++ b/core/src/utils/test/playwright/fixtures.ts @@ -43,7 +43,7 @@ export const test = base.extend({ * to be hydrated before proceeding with the test. */ page.goto = async (url: string) => { - const { mode, rtl } = testInfo.project.metadata; + const { mode, rtl, _testing } = testInfo.project.metadata; const splitUrl = url.split('?'); const paramsString = splitUrl[1]; @@ -55,8 +55,9 @@ export const test = base.extend({ const urlToParams = new URLSearchParams(paramsString); const formattedMode = urlToParams.get('ionic:mode') ?? mode; const formattedRtl = urlToParams.get('rtl') ?? rtl; + const ionicTesting = urlToParams.get('ionic:_testing') ?? _testing; - const formattedUrl = `${splitUrl[0]}?ionic:_testing=true&ionic:mode=${formattedMode}&rtl=${formattedRtl}`; + const formattedUrl = `${splitUrl[0]}?ionic:_testing=${ionicTesting}&ionic:mode=${formattedMode}&rtl=${formattedRtl}`; const results = await Promise.all([ page.waitForFunction(() => (window as any).testAppLoaded === true),