test(popover): migrate tests to Playwright (#25176)

Co-authored-by: Liam DeBeasi <liamdebeasi@users.noreply.github.com>
This commit is contained in:
Amanda Johnston
2022-05-16 09:31:00 -05:00
committed by GitHub
parent a6b66111ce
commit b35e946168
219 changed files with 505 additions and 1163 deletions

View File

@ -1,61 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('popover - arrow side: top', async () => {
await testPopover('top', false);
});
test('popover - arrow side: right', async () => {
await testPopover('right', false);
});
test('popover - arrow side: bottom', async () => {
await testPopover('bottom', false);
});
test('popover - arrow side: left', async () => {
await testPopover('left', false);
});
test('popover - arrow side: start', async () => {
await testPopover('start', false);
});
test('popover - arrow side: end', async () => {
await testPopover('end', false);
});
test('popover - arrow side: start, rtl', async () => {
await testPopover('start', true);
});
test('popover - arrow side: end, rtl', async () => {
await testPopover('end', true);
});
const testPopover = async (side: string, isRTL = false) => {
const rtl = isRTL ? '&rtl=true' : '';
const page = await newE2EPage({ url: `/src/components/popover/test/arrow?ionic:_testing=true${rtl}` });
const POPOVER_CLASS = `${side}-popover`;
const TRIGGER_ID = `${side}-trigger`;
const screenshotCompares = [];
const trigger = await page.find(`#${TRIGGER_ID}`);
await page.evaluate((POPOVER_TRIGGER_ID) => {
const popoverTrigger = document.querySelector(`#${POPOVER_TRIGGER_ID}`);
popoverTrigger?.scrollIntoView({ block: 'center' });
}, TRIGGER_ID);
trigger.click();
await page.waitForSelector(`.${POPOVER_CLASS}`);
const popover = await page.find(`.${POPOVER_CLASS}`);
await popover.waitForVisible();
screenshotCompares.push(await page.compareScreenshot());
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
};

View File

@ -13,10 +13,13 @@
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
ion-app > ion-content {
--background: #dddddd;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-row-gap: 20px;
grid-row-gap: 80px;
grid-column-gap: 20px;
padding: 200px;
@ -54,42 +57,48 @@
<div class="grid-item">
<h2>Top</h2>
<ion-button id="top-trigger">Click to Open</ion-button>
<ion-popover class="top-popover" trigger="top-trigger" side="top" size="cover">
<ion-popover show-backdrop="false" class="top-popover" trigger="top-trigger" side="top" size="cover">
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>
<div class="grid-item">
<h2>Right</h2>
<ion-button id="right-trigger">Click to Open</ion-button>
<ion-popover class="right-popover" trigger="right-trigger" side="right" size="cover">
<ion-popover show-backdrop="false" class="right-popover" trigger="right-trigger" side="right" size="cover">
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>
<div class="grid-item">
<h2>Bottom</h2>
<ion-button id="bottom-trigger">Click to Open</ion-button>
<ion-popover class="bottom-popover" trigger="bottom-trigger" side="bottom" size="cover">
<ion-popover
show-backdrop="false"
class="bottom-popover"
trigger="bottom-trigger"
side="bottom"
size="cover"
>
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>
<div class="grid-item">
<h2>Left</h2>
<ion-button id="left-trigger">Click to Open</ion-button>
<ion-popover class="left-popover" trigger="left-trigger" side="left" size="cover">
<ion-popover show-backdrop="false" class="left-popover" trigger="left-trigger" side="left" size="cover">
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>
<div class="grid-item">
<h2>Start</h2>
<ion-button id="start-trigger">Click to Open</ion-button>
<ion-popover class="start-popover" trigger="start-trigger" side="start" size="cover">
<ion-popover show-backdrop="false" class="start-popover" trigger="start-trigger" side="start" size="cover">
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>
<div class="grid-item">
<h2>End</h2>
<ion-button id="end-trigger">Click to Open</ion-button>
<ion-popover class="end-popover" trigger="end-trigger" side="end" size="cover">
<ion-popover show-backdrop="false" class="end-popover" trigger="end-trigger" side="end" size="cover">
<ion-content class="ion-padding"> Hello World </ion-content>
</ion-popover>
</div>

View File

@ -0,0 +1,23 @@
import { expect } from '@playwright/test';
import { test, Viewports } from '@utils/test/playwright';
import { openPopover } from '../test.utils';
test.describe('popover: arrow rendering', async () => {
/**
* The popovers have showBackdrop=false so we can open all of them at once
* and massively cut down on screenshots taken. The content has its own
* backdrop so you can still see the popovers.
*/
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/popover/test/arrow');
await page.setViewportSize(Viewports.tablet.portrait); // avoid extra-long viewport screenshots
const sides = ['top', 'right', 'bottom', 'left', 'start', 'end'];
for (const side of sides) {
await openPopover(page, `${side}-trigger`, true);
}
expect(await page.screenshot()).toMatchSnapshot(`popover-arrow-${page.getSnapshotSettings()}.png`);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -20,52 +20,6 @@
<body>
<ion-app>
<ion-content class="ion-padding" id="content">
<ion-button
id="basic-popover"
expand="block"
onclick="presentPopover({ component: 'profile-page', event: event, htmlAttributes: { 'data-testid': 'basic-popover' } })"
>
Show Popover</ion-button
>
<ion-button
id="translucent-popover"
expand="block"
onclick="presentPopover({ component: 'translucent-page', event: event, translucent: true })"
>Show Translucent Popover</ion-button
>
<ion-button
id="long-list-popover"
expand="block"
color="secondary"
onclick="presentPopover({ component: 'list-page', event: event })"
>Show Long List Popover</ion-button
>
<ion-button
id="no-event-popover"
expand="block"
color="danger"
onclick="presentPopover({ component: 'profile-page' })"
>No Event Popover</ion-button
>
<ion-button
id="custom-class-popover"
expand="block"
color="tertiary"
onclick="presentPopover({ component: 'translucent-page', event: event, cssClass: 'my-custom-class' })"
>Custom Class Popover</ion-button
>
<ion-button id="header-popover" expand="block" onclick="presentPopover({ component: 'header-page' })"
>Popover With Header</ion-button
>
<ion-button
id="translucent-header-popover"
expand="block"
onclick="presentPopover({ component: 'translucent-header-page' })"
>Popover With Translucent Header</ion-button
>
</ion-content>
<ion-content class="ion-padding" id="content">
<ion-button
id="basic-popover"

View File

@ -1,142 +1,65 @@
import type { E2EPage } from '@stencil/core/testing';
import { newE2EPage } from '@stencil/core/testing';
import { expect } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
import { test } from '@utils/test/playwright';
import { testPopover } from '../test.utils';
import { openPopover, screenshotPopover } from '../test.utils';
const DIRECTORY = 'basic';
test.describe('popover: rendering', async () => {
test('should not have visual regressions', async ({ page }) => {
const buttonIDs = [
'basic-popover',
'translucent-popover',
'long-list-popover',
'no-event-popover',
'custom-class-popover',
'header-popover',
'translucent-header-popover',
];
/**
* Focusing happens async inside of popover so we need
* to wait for the requestAnimationFrame to fire.
*/
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
};
const getActiveElementSelectionStart = (page: E2EPage) => {
return page.evaluate(() =>
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
);
};
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;
for (const id of buttonIDs) {
await screenshotPopover(page, id, 'basic');
}
});
};
test('popover: basic', async () => {
await testPopover(DIRECTORY, '#basic-popover');
});
test('popover: translucent', async () => {
await testPopover(DIRECTORY, '#translucent-popover');
test.describe('popover: htmlAttributes', async () => {
test('should inherit attributes on host', async ({ page }) => {
await page.goto('/src/components/popover/test/basic');
await openPopover(page, 'basic-popover');
const alert = page.locator('ion-popover');
expect(alert).toHaveAttribute('data-testid', 'basic-popover');
});
});
test('popover: long list', async () => {
await testPopover(DIRECTORY, '#long-list-popover');
});
test.describe('popover: focus trap', async () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/popover/test/basic');
});
test('popover: no event', async () => {
await testPopover(DIRECTORY, '#no-event-popover');
});
test('popover: custom class', async () => {
await testPopover(DIRECTORY, '#custom-class-popover');
});
test('popover: header', async () => {
await testPopover(DIRECTORY, '#header-popover');
});
test('popover: translucent header', async () => {
await testPopover(DIRECTORY, '#translucent-header-popover');
});
/**
* RTL Tests
*/
test('popover:rtl: basic', async () => {
await testPopover(DIRECTORY, '#basic-popover', true, true);
});
test('popover:rtl: translucent', async () => {
await testPopover(DIRECTORY, '#translucent-popover', true, true);
});
test('popover:rtl: long list', async () => {
await testPopover(DIRECTORY, '#long-list-popover', true, true);
});
test('popover:rtl: no event', async () => {
await testPopover(DIRECTORY, '#no-event-popover', true, true);
});
test('popover:rtl: custom class', async () => {
await testPopover(DIRECTORY, '#custom-class-popover', true, true);
});
test('popover:rtl: header', async () => {
await testPopover(DIRECTORY, '#header-popover', true);
});
test('popover:rtl: translucent header', async () => {
await testPopover(DIRECTORY, '#translucent-header-popover', true);
});
test('popover: htmlAttributes', 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 alert = await page.find('ion-popover');
expect(alert).not.toBe(null);
await alert.waitForVisible();
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();
test('should focus the first ion-item on ArrowDown', async ({ page }) => {
await openPopover(page, 'basic-popover');
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();
test('should trap focus', async ({ page, browserName }) => {
await openPopover(page, 'basic-popover');
await page.keyboard.press('Tab');
await expectActiveElementTextToEqual(page, 'Item 0');
await page.keyboard.down('Shift');
if (browserName === 'webkit') {
await page.keyboard.down('Alt');
}
await page.keyboard.press('Tab');
await page.keyboard.up('Shift');
if (browserName === 'webkit') {
await page.keyboard.up('Alt');
}
await expectActiveElementTextToEqual(page, 'Item 3');
@ -161,19 +84,17 @@ describe('popover: focus trap', () => {
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"');
test('should not override keyboard interactions for textarea elements', async ({ page, browserName }) => {
await openPopover(page, 'popover-with-textarea');
await page.waitForFunction(() => document.activeElement?.tagName === 'ION-POPOVER');
await page.keyboard.press('Tab');
// Checking within ion-textarea
// for Firefox, ion-textarea is focused first
// need to tab again to get to native input
if (browserName === 'firefox') {
await page.keyboard.press('Tab');
}
let activeElementTagName = await page.evaluate(() => document.activeElement!.tagName);
let scrollTop = null;
@ -235,3 +156,27 @@ describe('popover: focus trap', () => {
expect(scrollTop).toBeGreaterThanOrEqual(previousScrollTop);
});
});
// TODO(FW-1424): convert these to Playwright assertions where possible
/**
* Focusing happens async inside of popover so we need
* to wait for the requestAnimationFrame to fire.
*/
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
};
const getActiveElementSelectionStart = (page: E2EPage) => {
return page.evaluate(() =>
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
);
};
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;
});
};

Some files were not shown because too many files have changed in this diff Show More