diff --git a/core/playwright.config.ts b/core/playwright.config.ts index 677625fed4..52bae64692 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/popover/test/basic/e2e.ts b/core/src/components/popover/test/basic/e2e.ts index a14c350560..190fc6c14b 100644 --- a/core/src/components/popover/test/basic/e2e.ts +++ b/core/src/components/popover/test/basic/e2e.ts @@ -1,3 +1,4 @@ +import type { E2EPage } from '@stencil/core/testing'; import { newE2EPage } from '@stencil/core/testing'; import { testPopover } from '../test.utils'; @@ -8,51 +9,23 @@ const DIRECTORY = 'basic'; * Focusing happens async inside of popover so we need * to wait for the requestAnimationFrame to fire. */ -const expectActiveElementTextToEqual = async (page, textValue) => { - await page.waitFor((text) => document.activeElement.textContent === text, {}, textValue); +const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => { + await page.evaluate((text) => document.activeElement!.textContent === text, textValue) }; -test('popover: focus trap', async () => { - const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' }); +const getActiveElementSelectionStart = (page: E2EPage) => { + return page.evaluate(() => + document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null + ); +}; - await page.click('#basic-popover'); - await page.waitForSelector('#basic-popover'); - - const popover = await page.find('ion-popover'); - - expect(popover).not.toBe(null); - await popover.waitForVisible(); - - await page.keyboard.press('Tab'); - - await expectActiveElementTextToEqual(page, 'Item 0'); - - await page.keyboard.down('Shift'); - await page.keyboard.press('Tab'); - await page.keyboard.up('Shift'); - - await expectActiveElementTextToEqual(page, 'Item 3'); - - await page.keyboard.press('Tab'); - - await expectActiveElementTextToEqual(page, 'Item 0'); - - await page.keyboard.press('ArrowDown'); - - await expectActiveElementTextToEqual(page, 'Item 1'); - - await page.keyboard.press('ArrowDown'); - - await expectActiveElementTextToEqual(page, 'Item 2'); - - await page.keyboard.press('Home'); - - await expectActiveElementTextToEqual(page, 'Item 0'); - - await page.keyboard.press('End'); - - await expectActiveElementTextToEqual(page, 'Item 3'); -}); +const getActiveElementScrollTop = (page: E2EPage) => { + return page.evaluate(() => { + // Returns the closest ion-textarea or active element + const target = document.activeElement!.closest('ion-textarea') ?? document.activeElement; + return target!.scrollTop; + }); +}; test('popover: basic', async () => { await testPopover(DIRECTORY, '#basic-popover'); @@ -125,7 +98,141 @@ test('popover: htmlAttributes', async () => { expect(alert).not.toBe(null); await alert.waitForVisible(); - const attribute = await page.evaluate(() => document.querySelector('ion-popover').getAttribute('data-testid')); + const attribute = await page.evaluate(() => document.querySelector('ion-popover')!.getAttribute('data-testid')); expect(attribute).toEqual('basic-popover'); }); + +describe('popover: focus trap', () => { + + it('should focus the first ion-item on ArrowDown', async () => { + const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' }); + + await page.click('#basic-popover'); + + const popover = await page.find('ion-popover'); + + expect(popover).not.toBe(null); + await popover.waitForVisible(); + + await page.keyboard.press('ArrowDown'); + + await expectActiveElementTextToEqual(page, 'Item 0'); + }); + + it('should work with ion-item children', async () => { + const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' }); + + await page.click('#basic-popover'); + await page.waitForSelector('#basic-popover'); + + const popover = await page.find('ion-popover'); + + expect(popover).not.toBe(null); + await popover.waitForVisible(); + + await page.keyboard.press('Tab'); + + await expectActiveElementTextToEqual(page, 'Item 0'); + + await page.keyboard.down('Shift'); + await page.keyboard.press('Tab'); + await page.keyboard.up('Shift'); + + await expectActiveElementTextToEqual(page, 'Item 3'); + + await page.keyboard.press('Tab'); + + await expectActiveElementTextToEqual(page, 'Item 0'); + + await page.keyboard.press('ArrowDown'); + + await expectActiveElementTextToEqual(page, 'Item 1'); + + await page.keyboard.press('ArrowDown'); + + await expectActiveElementTextToEqual(page, 'Item 2'); + + await page.keyboard.press('Home'); + + await expectActiveElementTextToEqual(page, 'Item 0'); + + await page.keyboard.press('End'); + + await expectActiveElementTextToEqual(page, 'Item 3'); + }); + + it('should not override keyboard interactions for textarea elements', async () => { + const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' }); + + await page.waitForSelector('#popover-with-textarea'); + await page.click('#popover-with-textarea'); + + const popover = await page.find('ion-popover'); + await popover.waitForVisible(); + + await page.waitForFunction('document.activeElement.tagName === "ION-POPOVER"'); + + await page.keyboard.press('Tab'); + // Checking within ion-textarea + + let activeElementTagName = await page.evaluate(() => document.activeElement!.tagName); + let scrollTop = null; + let selectionStart = null; + let previousSelectionStart = null; + + // This is the native textarea within ion-textarea + expect(activeElementTagName).toBe('TEXTAREA'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBe(0); + + await page.keyboard.press('ArrowDown'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBeGreaterThan(0); + previousSelectionStart = selectionStart; + + await page.keyboard.press('ArrowDown'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBeGreaterThan(previousSelectionStart!); + + await page.keyboard.press('Tab'); + // Checking within HTML textarea + + // Reset tracking variables as the focus element has changed + scrollTop = null; + selectionStart = null; + previousSelectionStart = null; + + activeElementTagName = await page.evaluate(() => document.activeElement!.tagName); + expect(activeElementTagName).toBe('TEXTAREA'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBe(0); + + await page.keyboard.press('ArrowDown'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBeGreaterThan(0); + previousSelectionStart = selectionStart; + + await page.keyboard.press('ArrowDown'); + + selectionStart = await getActiveElementSelectionStart(page); + expect(selectionStart).toBeGreaterThan(previousSelectionStart!); + + await page.keyboard.press('Home'); + + scrollTop = await getActiveElementScrollTop(page); + expect(scrollTop).toBeGreaterThan(0); + + const previousScrollTop = scrollTop; + + await page.keyboard.press('End'); + + scrollTop = await getActiveElementScrollTop(page); + expect(scrollTop).toBeGreaterThanOrEqual(previousScrollTop); + }); +}); diff --git a/core/src/components/popover/test/basic/index.html b/core/src/components/popover/test/basic/index.html index e400fd9bfb..5ffefb9577 100644 --- a/core/src/components/popover/test/basic/index.html +++ b/core/src/components/popover/test/basic/index.html @@ -1,91 +1,67 @@ -
- -