test(playwright): add new utilities for skipping tests (#25758)

This commit is contained in:
Liam DeBeasi
2022-08-16 08:18:42 -05:00
committed by GitHub
parent e750e33616
commit 79c65dc382
40 changed files with 222 additions and 145 deletions

View File

@ -33,12 +33,14 @@ const projects = [
{ {
name: 'Mobile Chrome', name: 'Mobile Chrome',
use: { use: {
browserName: 'chromium',
...devices['Pixel 5'] ...devices['Pixel 5']
} }
}, },
{ {
name: 'Mobile Safari', name: 'Mobile Safari',
use: { use: {
browserName: 'webkit',
...devices['iPhone 12'] ...devices['iPhone 12']
} }
} }

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('accordion: a11y', () => { test.describe('accordion: a11y', () => {
test('accordions should be keyboard navigable', async ({ page, browserName }) => { test('accordions should be keyboard navigable', async ({ page, skip, browserName }) => {
// TODO(FW-1764): remove skip once issue is resolved // TODO(FW-1764): remove skip once issue is resolved
test.skip(browserName === 'firefox', 'https://github.com/ionic-team/ionic-framework/issues/25529'); skip.browser('firefox', 'https://github.com/ionic-team/ionic-framework/issues/25529');
await page.goto(`/src/components/accordion/test/a11y`); await page.goto(`/src/components/accordion/test/a11y`);
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';

View File

@ -10,8 +10,8 @@ test.describe('action sheet: basic', () => {
actionSheetFixture = new ActionSheetFixture(page); actionSheetFixture = new ActionSheetFixture(page);
}); });
test.describe('action sheet: data', () => { test.describe('action sheet: data', () => {
test('should return data', async ({ page }, testInfo) => { test('should return data', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
const ionActionSheetDidDismiss = await page.spyOnEvent('ionActionSheetDidDismiss'); const ionActionSheetDidDismiss = await page.spyOnEvent('ionActionSheetDidDismiss');
await actionSheetFixture.open('#buttonData'); await actionSheetFixture.open('#buttonData');
@ -22,8 +22,8 @@ test.describe('action sheet: basic', () => {
await ionActionSheetDidDismiss.next(); await ionActionSheetDidDismiss.next();
expect(ionActionSheetDidDismiss).toHaveReceivedEventDetail({ data: { type: '1' }, role: undefined }); expect(ionActionSheetDidDismiss).toHaveReceivedEventDetail({ data: { type: '1' }, role: undefined });
}); });
test('should return cancel button data', async ({ page }, testInfo) => { test('should return cancel button data', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
const ionActionSheetDidDismiss = await page.spyOnEvent('ionActionSheetDidDismiss'); const ionActionSheetDidDismiss = await page.spyOnEvent('ionActionSheetDidDismiss');
await actionSheetFixture.open('#buttonData'); await actionSheetFixture.open('#buttonData');
@ -36,8 +36,8 @@ test.describe('action sheet: basic', () => {
}); });
}); });
test.describe('action sheet: attributes', () => { test.describe('action sheet: attributes', () => {
test('should set htmlAttributes', async ({ page }, testInfo) => { test('should set htmlAttributes', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
await actionSheetFixture.open('#basic'); await actionSheetFixture.open('#basic');
const actionSheet = page.locator('ion-action-sheet'); const actionSheet = page.locator('ion-action-sheet');
@ -73,15 +73,15 @@ test.describe('action sheet: basic', () => {
await actionSheetFixture.open('#scrollWithoutCancel'); await actionSheetFixture.open('#scrollWithoutCancel');
await actionSheetFixture.screenshot('scroll-without-cancel'); await actionSheetFixture.screenshot('scroll-without-cancel');
}); });
test('should open custom backdrop action sheet', async ({ page }, testInfo) => { test('should open custom backdrop action sheet', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
await actionSheetFixture.open('#customBackdrop'); await actionSheetFixture.open('#customBackdrop');
const backdrop = page.locator('ion-action-sheet ion-backdrop'); const backdrop = page.locator('ion-action-sheet ion-backdrop');
expect(backdrop).toHaveCSS('opacity', '1'); expect(backdrop).toHaveCSS('opacity', '1');
}); });
test('should open alert from action sheet', async ({ page }, testInfo) => { test('should open alert from action sheet', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent'); const ionAlertDidPresent = await page.spyOnEvent('ionAlertDidPresent');
await actionSheetFixture.open('#alertFromActionSheet'); await actionSheetFixture.open('#alertFromActionSheet');
@ -89,8 +89,8 @@ test.describe('action sheet: basic', () => {
await ionAlertDidPresent.next(); await ionAlertDidPresent.next();
}); });
test('should not dismiss action sheet when backdropDismiss: false', async ({ page }, testInfo) => { test('should not dismiss action sheet when backdropDismiss: false', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
await actionSheetFixture.open('#noBackdropDismiss'); await actionSheetFixture.open('#noBackdropDismiss');
const actionSheet = page.locator('ion-action-sheet'); const actionSheet = page.locator('ion-action-sheet');
@ -100,8 +100,8 @@ test.describe('action sheet: basic', () => {
}); });
}); });
test.describe('action sheet: focus trap', () => { test.describe('action sheet: focus trap', () => {
test('it should trap focus in action sheet', async ({ page, browserName }, testInfo) => { test('it should trap focus in action sheet', async ({ page, skip, browserName }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await actionSheetFixture.open('#basic'); await actionSheetFixture.open('#basic');

View File

@ -2,12 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('action sheet: translucent', () => { test.describe('action sheet: translucent', () => {
test('should not have visual regressions', async ({ page }, testInfo) => { test('should not have visual regressions', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode === 'md', 'Translucent effect is only active on iOS mode'); skip.mode('md', 'Translucent effect is only active on iOS mode');
test.skip( skip.rtl('This tests how the component is painted, not layout. RTL tests are not needed here');
testInfo.project.metadata.rtl === true,
'This tests how the component is painted, not layout. RTL tests are not needed here'
);
await page.goto(`/src/components/action-sheet/test/translucent`); await page.goto(`/src/components/action-sheet/test/translucent`);

View File

@ -14,11 +14,8 @@ test.describe('app: safe-area', () => {
expect(await page.screenshot()).toMatchSnapshot(`app-${screenshotModifier}-diff-${page.getSnapshotSettings()}.png`); expect(await page.screenshot()).toMatchSnapshot(`app-${screenshotModifier}-diff-${page.getSnapshotSettings()}.png`);
}; };
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip( skip.rtl('Safe area tests only check top and bottom edges. RTL checks are not required here.');
testInfo.project.metadata.rtl === true,
'Safe area tests only check top and bottom edges. RTL checks are not required here.'
);
await page.goto(`/src/components/app/test/safe-area`); await page.goto(`/src/components/app/test/safe-area`);
}); });

View File

@ -12,8 +12,8 @@ test.describe('button: basic', () => {
}); });
test.describe('button: ripple effect', () => { test.describe('button: ripple effect', () => {
test('should not have visual regressions', async ({ page }, testInfo) => { test('should not have visual regressions', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'md', 'Ripple effect is only available in MD mode.'); skip.mode('ios', 'Ripple effect is only available in MD mode.');
await page.goto(`/src/components/button/test/basic?ionic:_testing=false`); await page.goto(`/src/components/button/test/basic?ionic:_testing=false`);

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('datetime-button: switching to correct view', () => { test.describe('datetime-button: switching to correct view', () => {
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.rtl();
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); skip.mode('ios', 'No mode-specific logic');
await page.setContent(` await page.setContent(`
<ion-datetime-button datetime="datetime"></ion-datetime-button> <ion-datetime-button datetime="datetime"></ion-datetime-button>
@ -31,10 +31,9 @@ test.describe('datetime-button: switching to correct view', () => {
}); });
test.describe('datetime-button: labels', () => { test.describe('datetime-button: labels', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.mode('ios', 'No mode-specific logic');
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic');
}); });
test('should set date and time labels in separate buttons', async ({ page }) => { test('should set date and time labels in separate buttons', async ({ page }) => {
await page.setContent(` await page.setContent(`
@ -106,10 +105,9 @@ test.describe('datetime-button: labels', () => {
}); });
test.describe('datetime-button: locale', () => { test.describe('datetime-button: locale', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.mode('ios', 'No mode-specific logic');
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic');
}); });
test('should use the same locale as datetime', async ({ page }) => { test('should use the same locale as datetime', async ({ page }) => {
await page.setContent(` await page.setContent(`
@ -154,10 +152,9 @@ test.describe('datetime-button: locale', () => {
}); });
test.describe('datetime-button: wheel', () => { test.describe('datetime-button: wheel', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.mode('ios', 'No mode-specific logic');
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic');
}); });
test('should only show a single date button when presentation="date-time" and prefer-wheel="true"', async ({ test('should only show a single date button when presentation="date-time" and prefer-wheel="true"', async ({
page, page,

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('datetime-button: disabled buttons', () => { test.describe('datetime-button: disabled buttons', () => {
test('buttons should not be enabled when component is disabled', async ({ page }, testInfo) => { test('buttons should not be enabled when component is disabled', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.rtl();
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); skip.mode('ios', 'No mode-specific logic');
await page.setContent(` await page.setContent(`
<ion-datetime-button datetime="datetime" disabled="true"></ion-datetime-button> <ion-datetime-button datetime="datetime" disabled="true"></ion-datetime-button>

View File

@ -42,9 +42,9 @@ test.describe('datetime-button: popover', () => {
let popover: Locator; let popover: Locator;
let ionPopoverDidPresent: EventSpy; let ionPopoverDidPresent: EventSpy;
let ionPopoverDidDismiss: EventSpy; let ionPopoverDidDismiss: EventSpy;
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.rtl();
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); skip.mode('ios', 'No mode-specific logic');
await page.setContent(` await page.setContent(`
<ion-datetime-button datetime="datetime"></ion-datetime-button> <ion-datetime-button datetime="datetime"></ion-datetime-button>
@ -104,9 +104,9 @@ test.describe('datetime-button: modal', () => {
let modal: Locator; let modal: Locator;
let ionModalDidPresent: EventSpy; let ionModalDidPresent: EventSpy;
let ionModalDidDismiss: EventSpy; let ionModalDidDismiss: EventSpy;
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); skip.rtl();
test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); skip.mode('ios', 'No mode-specific logic');
await page.setContent(` await page.setContent(`
<ion-datetime-button datetime="datetime"></ion-datetime-button> <ion-datetime-button datetime="datetime"></ion-datetime-button>

View File

@ -185,10 +185,9 @@ test.describe('datetime: footer', () => {
}); });
test.describe('datetime: swiping', () => { test.describe('datetime: swiping', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL layouts.'); skip.mode('ios', 'This does not have mode-specific logic.');
test.skip(testInfo.project.metadata.mode === 'ios', 'This does not have mode-specific logic.');
}); });
test('should move to prev month by swiping', async ({ page }) => { test('should move to prev month by swiping', async ({ page }) => {
await page.setContent(` await page.setContent(`
@ -224,8 +223,8 @@ test.describe('datetime: swiping', () => {
await expect(calendarHeader).toHaveText(/June 2022/); await expect(calendarHeader).toHaveText(/June 2022/);
}); });
test('should not re-render if swipe is in progress', async ({ page, browserName }) => { test('should not re-render if swipe is in progress', async ({ page, skip }) => {
test.skip(browserName === 'webkit', 'Wheel is not available in WebKit'); skip.browser('webkit', 'Wheel is not available in WebKit');
await page.setContent(` await page.setContent(`
<ion-datetime value="2022-05-03"></ion-datetime> <ion-datetime value="2022-05-03"></ion-datetime>

View File

@ -13,13 +13,8 @@ const queryAllWorkingMonthDisabledDays = (page: E2EPage, datetimeSelector = 'ion
}; };
test.describe('datetime: disable dates', () => { test.describe('datetime: disable dates', () => {
/** test.beforeEach(({ skip }) => {
* We need to access testInfo, but Playwright skip.rtl();
* requires that we destructure the first parameter.
*/
// eslint-disable-next-line no-empty-pattern
test.beforeEach(({}, testInfo) => {
test.skip(testInfo.project.metadata.rtl === true, 'These tests do not check layout rendering functionality.');
}); });
test.describe('check return values', () => { test.describe('check return values', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {

View File

@ -32,9 +32,8 @@ test.describe('datetime: multiple date selection (visual regressions)', () => {
}); });
test.describe('datetime: multiple date selection (functionality)', () => { test.describe('datetime: multiple date selection (functionality)', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(async ({ skip }) => {
test.beforeEach(async ({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.');
}); });
test('clicking unselected days should select them', async ({ page }) => { test('clicking unselected days should select them', async ({ page }) => {

View File

@ -84,8 +84,8 @@ test.describe('datetime: presentation', () => {
expect(ionChangeSpy.length).toBe(1); expect(ionChangeSpy.length).toBe(1);
}); });
test('switching presentation should close month/year picker', async ({ page }, testInfo) => { test('switching presentation should close month/year picker', async ({ page, skip }) => {
await test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL specific behaviors.'); await skip.rtl();
await page.setContent(` await page.setContent(`
<ion-datetime presentation="date"></ion-datetime> <ion-datetime presentation="date"></ion-datetime>

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('header: condense', () => { test.describe('header: condense', () => {
test('should be hidden from screen readers when collapsed', async ({ page }, testInfo) => { test('should be hidden from screen readers when collapsed', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode === 'md', 'Logic only applies to iOS mode'); skip.mode('md');
test.skip(testInfo.project.metadata.rtl === true, 'No RTL-specific logic'); skip.rtl();
await page.goto('/src/components/header/test/condense'); await page.goto('/src/components/header/test/condense');
const header = page.locator('#collapsibleHeader'); const header = page.locator('#collapsibleHeader');

View File

@ -2,10 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('input: a11y', () => { test.describe('input: a11y', () => {
test('does not set a default aria-labelledby when there is not a neighboring ion-label', async ({ test('does not set a default aria-labelledby when there is not a neighboring ion-label', async ({ page, skip }) => {
page, skip.rtl();
}, testInfo) => {
test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.');
await page.setContent('<ion-input></ion-input>'); await page.setContent('<ion-input></ion-input>');
@ -15,8 +13,8 @@ test.describe('input: a11y', () => {
await expect(ariaLabelledBy).toBe(null); await expect(ariaLabelledBy).toBe(null);
}); });
test('set a default aria-labelledby when a neighboring ion-label exists', async ({ page }, testInfo) => { test('set a default aria-labelledby when a neighboring ion-label exists', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.'); skip.rtl();
await page.setContent( await page.setContent(
` `

View File

@ -6,8 +6,8 @@ test.describe('input: masking', () => {
await page.goto('/src/components/input/test/masking'); await page.goto('/src/components/input/test/masking');
}); });
test('should filter out spaces', async ({ page }, testInfo) => { test('should filter out spaces', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.'); skip.rtl();
const input = page.locator('#inputTrimmed'); const input = page.locator('#inputTrimmed');

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('item-sliding: basic', () => { test.describe('item-sliding: basic', () => {
test('should not scroll when the item-sliding is swiped', async ({ page, browserName }, testInfo) => { test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
test.skip(browserName === 'webkit', 'mouse.wheel is not available in WebKit'); skip.browser('webkit', 'mouse.wheel is not available in WebKit');
test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL-specific behaviors'); skip.rtl();
await page.goto(`/src/components/item-sliding/test/basic`); await page.goto(`/src/components/item-sliding/test/basic`);

View File

@ -2,12 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('item-sliding: scroll-target', () => { test.describe('item-sliding: scroll-target', () => {
test('should not scroll when the item-sliding is swiped in custom scroll target', async ({ test('should not scroll when the item-sliding is swiped in custom scroll target', async ({ page, skip }) => {
page, skip.browser('webkit', 'mouse.wheel is not available in WebKit');
browserName, skip.rtl();
}, testInfo) => {
test.skip(browserName === 'webkit', 'mouse.wheel is not available in WebKit');
test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL-specific behaviors');
await page.goto(`/src/components/item-sliding/test/scroll-target`); await page.goto(`/src/components/item-sliding/test/scroll-target`);

View File

@ -41,9 +41,8 @@ test.describe('item: inputs', () => {
}); });
test.describe('form data', () => { test.describe('form data', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(async ({ skip }) => {
test.beforeEach(async ({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.');
}); });
test('initial form data should be empty', async ({ page }) => { test('initial form data should be empty', async ({ page }) => {

View File

@ -3,9 +3,9 @@ import { test, Viewports } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright'; import type { E2EPage } from '@utils/test/playwright';
test.describe('modal: focus trapping', () => { test.describe('modal: focus trapping', () => {
test.beforeEach(async ({ browserName }, testInfo) => { test.beforeEach(async ({ skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
test.skip(browserName === 'firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content'); skip.browser('firefox', 'Firefox incorrectly allows keyboard focus to move to ion-content');
}); });
test('focus should be trapped inside of modal', async ({ page, browserName }) => { test('focus should be trapped inside of modal', async ({ page, browserName }) => {
/** /**

View File

@ -134,8 +134,8 @@ test.describe('modal: canDismiss', () => {
}); });
test.describe('card modal - iOS swiping', () => { test.describe('card modal - iOS swiping', () => {
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Swipe to close on a modal is only available in iOS mode.'); skip.mode('md');
await page.click('#radio-card'); await page.click('#radio-card');
}); });

View File

@ -5,13 +5,13 @@ import { CardModalPage } from '../fixtures';
test.describe('card modal - nav', () => { test.describe('card modal - nav', () => {
let cardModalPage: CardModalPage; let cardModalPage: CardModalPage;
test.beforeEach(async ({ page, browserName }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); skip.mode('md');
test.skip( skip.rtl('This test only verifies that the gesture activates inside of a modal.');
testInfo.project.metadata.rtl === true, skip.browser(
'This test only verifies that the gesture activates inside of a modal.' (browserName: string) => browserName !== 'chromium',
'dragElementBy is flaky outside of Chrome browsers.'
); );
test.skip(browserName !== 'chromium', 'dragElementBy is flaky outside of Chrome browsers.');
cardModalPage = new CardModalPage(page); cardModalPage = new CardModalPage(page);
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false'); await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false');

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { dragElementBy, test } from '@utils/test/playwright'; import { dragElementBy, test } from '@utils/test/playwright';
test.describe('card modal - with refresher', () => { test.describe('card modal - with refresher', () => {
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); skip.mode('md');
await page.goto('/src/components/modal/test/card-refresher'); await page.goto('/src/components/modal/test/card-refresher');
}); });

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { dragElementBy, test } from '@utils/test/playwright'; import { dragElementBy, test } from '@utils/test/playwright';
test.describe('card modal - scroll target', () => { test.describe('card modal - scroll target', () => {
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); skip.mode('md');
await page.goto('/src/components/modal/test/card-scroll-target'); await page.goto('/src/components/modal/test/card-scroll-target');
}); });

View File

@ -5,8 +5,8 @@ import { CardModalPage } from '../fixtures';
test.describe('card modal', () => { test.describe('card modal', () => {
let cardModalPage: CardModalPage; let cardModalPage: CardModalPage;
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); skip.mode('md');
cardModalPage = new CardModalPage(page); cardModalPage = new CardModalPage(page);
await cardModalPage.navigate('/src/components/modal/test/card'); await cardModalPage.navigate('/src/components/modal/test/card');

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('modal: custom dialog', () => { test.describe('modal: custom dialog', () => {
test('should size custom modal correctly', async ({ page }, testInfo) => { test('should size custom modal correctly', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs. RTL layout.'); skip.rtl();
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/24080', description: 'https://github.com/ionic-team/ionic-framework/issues/24080',

View File

@ -32,8 +32,8 @@ test.describe('picker-column-internal', () => {
expect(activeColumn).not.toBeNull(); expect(activeColumn).not.toBeNull();
}); });
test('scrolling should change the active item', async ({ page, browserName }) => { test('scrolling should change the active item', async ({ page, skip }) => {
test.skip(browserName === 'firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890'); skip.browser('firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890');
await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => { await page.locator('#default').evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.scrollTop = 801; el.scrollTop = 801;
@ -45,8 +45,8 @@ test.describe('picker-column-internal', () => {
expect(await activeColumn?.innerText()).toEqual('23'); expect(await activeColumn?.innerText()).toEqual('23');
}); });
test('should not emit ionChange when the value is modified externally', async ({ page, browserName }) => { test('should not emit ionChange when the value is modified externally', async ({ page, skip }) => {
test.skip(browserName === 'firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890'); skip.browser('firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890');
const ionChangeSpy = await page.spyOnEvent('ionChange'); const ionChangeSpy = await page.spyOnEvent('ionChange');
@ -57,8 +57,8 @@ test.describe('picker-column-internal', () => {
expect(ionChangeSpy).not.toHaveReceivedEvent(); expect(ionChangeSpy).not.toHaveReceivedEvent();
}); });
test('should emit ionChange when the picker is scrolled', async ({ page, browserName }) => { test('should emit ionChange when the picker is scrolled', async ({ page, skip }) => {
test.skip(browserName === 'firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890'); skip.browser('firefox', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1766890');
const ionChangeSpy = await page.spyOnEvent('ionChange'); const ionChangeSpy = await page.spyOnEvent('ionChange');

View File

@ -23,10 +23,10 @@ test.describe('popover: dismissOnSelect', async () => {
await expect(popover).toBeVisible(); await expect(popover).toBeVisible();
}); });
test('should not dismiss a popover when clicking a click trigger', async ({ page, browserName }) => { test('should not dismiss a popover when clicking a click trigger', async ({ page, skip }) => {
// TODO FW-1486 // TODO FW-1486
test.skip( skip.browser(
browserName === 'firefox', 'firefox',
'Parent popover disappears when click trigger is clicked. Cannot replicate locally. Needs further investigation.' 'Parent popover disappears when click trigger is clicked. Cannot replicate locally. Needs further investigation.'
); );

View File

@ -16,8 +16,8 @@ test.describe('radio-group: basic', () => {
test.describe('radio-group: interaction', () => { test.describe('radio-group: interaction', () => {
let radioFixture: RadioFixture; let radioFixture: RadioFixture;
test.beforeEach(({ page }, testInfo) => { test.beforeEach(({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.'); skip.rtl();
radioFixture = new RadioFixture(page); radioFixture = new RadioFixture(page);
}); });

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('radio-group', () => { test.describe('radio-group', () => {
test.beforeEach(async ({ page }, testInfo) => { test.beforeEach(async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.'); skip.rtl();
await page.goto('/src/components/radio-group/test/search'); await page.goto('/src/components/radio-group/test/search');
}); });

View File

@ -2,9 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('radio: a11y', () => { test.describe('radio: a11y', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.');
}); });
test('tabbing should switch between radio groups', async ({ page, browserName }) => { test('tabbing should switch between radio groups', async ({ page, browserName }) => {
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';

View File

@ -97,9 +97,8 @@ test.describe('radio: rendering', () => {
}); });
test.describe('radio: interaction', () => { test.describe('radio: interaction', () => {
// eslint-disable-next-line no-empty-pattern test.beforeEach(({ skip }) => {
test.beforeEach(({}, testInfo) => { skip.rtl();
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.');
}); });
test('radio should be checked when activated', async ({ page }) => { test('radio should be checked when activated', async ({ page }) => {
await page.setContent(` await page.setContent(`

View File

@ -54,9 +54,9 @@ test.describe('range: basic', () => {
expect(rangeEnd).toHaveReceivedEventDetail({ value: 21 }); expect(rangeEnd).toHaveReceivedEventDetail({ value: 21 });
}); });
test('should not scroll when the knob is swiped', async ({ page, browserName }, testInfo) => { test('should not scroll when the knob is swiped', async ({ page, skip }) => {
test.skip(browserName === 'webkit', 'mouse.wheel is not available in WebKit'); skip.browser('webkit', 'mouse.wheel is not available in WebKit');
test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL-specific behaviors'); skip.rtl();
await page.goto(`/src/components/range/test/basic`); await page.goto(`/src/components/range/test/basic`);

View File

@ -2,9 +2,9 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('range: scroll-target', () => { test.describe('range: scroll-target', () => {
test('should not scroll when the knob is swiped in custom scroll target', async ({ page, browserName }, testInfo) => { test('should not scroll when the knob is swiped in custom scroll target', async ({ page, skip }) => {
test.skip(browserName === 'webkit', 'mouse.wheel is not available in WebKit'); skip.browser('webkit', 'mouse.wheel is not available in WebKit');
test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL-specific behaviors'); skip.rtl();
await page.goto(`/src/components/range/test/scroll-target`); await page.goto(`/src/components/range/test/scroll-target`);

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('select: async', () => { test.describe('select: async', () => {
test('should correctly set the value after a delay', async ({ page }, testInfo) => { test('should correctly set the value after a delay', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This is checking internal logic. RTL tests are not needed'); skip.rtl('This is checking internal logic. RTL tests are not needed');
await page.goto(`/src/components/select/test/async`); await page.goto(`/src/components/select/test/async`);
const selectValueSet = await page.spyOnEvent('selectValueSet'); const selectValueSet = await page.spyOnEvent('selectValueSet');

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('select: compare-with', () => { test.describe('select: compare-with', () => {
test('should correctly set value when using compareWith property', async ({ page }, testInfo) => { test('should correctly set value when using compareWith property', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'This is checking internal logic. RTL tests are not needed'); skip.rtl('This is checking internal logic. RTL tests are not needed');
await page.goto('/src/components/select/test/compare-with'); await page.goto('/src/components/select/test/compare-with');

View File

@ -2,8 +2,8 @@ import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright'; import { test } from '@utils/test/playwright';
test.describe('overlays: focus', () => { test.describe('overlays: focus', () => {
test('should not focus the overlay container if element inside of overlay is focused', async ({ page }, testInfo) => { test('should not focus the overlay container if element inside of overlay is focused', async ({ page, skip }) => {
test.skip(testInfo.project.metadata.rtl === true, 'RTL tests are not needed as layout is not checked'); skip.rtl();
await page.setContent(` await page.setContent(`
<ion-button id="open-modal">Show Modal</ion-button> <ion-button id="open-modal">Show Modal</ion-button>

View File

@ -0,0 +1,69 @@
# Playwright Test Utils
This directory contains utilities that can be used to more easily test Stencil projects with Playwright.
## Test Function
The default `test` function has been extended to provide two custom options.
| Fixture | Type | Description |
| ------- | ---- | ----------- |
| page | [E2EPage](https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/test/playwright/playwright-declarations.ts) | An extension of the base `page` test fixture within Playwright |
| skip | [E2ESkip](https://github.com/ionic-team/ionic-framework/blob/main/core/src/utils/test/playwright/playwright-declarations.ts) | Used to skip tests based on text direction, mode, or browser |
### Usage
**`page`**
```typescript
import { test } from '@utils/test/playwright';
test('my custom test', ({ page }) => {
await page.goto('path/to/file');
});
```
**`skip.mode`**
```typescript
import { test } from '@utils/test/playwright';
test('my custom test', ({ page, skip }) => {
skip.mode('md', 'This test is iOS-specific.');
await page.goto('path/to/file');
});
```
**`skip.rtl`**
```typescript
import { test } from '@utils/test/playwright';
test('my custom test', ({ page, skip }) => {
skip.rtl('This test does not have RTL-specific behaviors.');
await page.goto('path/to/file');
});
```
**`skip.browser`**
```typescript
import { test } from '@utils/test/playwright';
test('my custom test', ({ page, skip }) => {
skip.browser('webkit', 'This test does not work in WebKit yet.');
await page.goto('path/to/file');
});
```
**`skip.browser` with callback**
```typescript
import { test } from '@utils/test/playwright';
test('my custom test', ({ page, skip }) => {
skip.browser((browserName: string) => browserName !== 'webkit', 'This tests a WebKit-specific behavior.');
await page.goto('path/to/file');
});
```

View File

@ -96,6 +96,14 @@ export interface E2EPage extends Page {
_e2eEvents: Map<number, any>; _e2eEvents: Map<number, any>;
} }
export type BrowserNameOrCallback = string | ((browserName: string) => boolean);
export interface E2ESkip {
rtl: (reason?: string) => void;
browser: (browserNameOrCallback: BrowserNameOrCallback, reason?: string) => void;
mode: (mode: string, reason?: string) => void;
}
export interface SetIonViewportOptions { export interface SetIonViewportOptions {
/** /**
* `true` if the viewport should be scaled to match the `ion-content` * `true` if the viewport should be scaled to match the `ion-content`

View File

@ -18,7 +18,7 @@ import {
locator, locator,
} from './page/utils'; } from './page/utils';
import type { LocatorOptions } from './page/utils'; import type { LocatorOptions } from './page/utils';
import type { E2EPage, SetIonViewportOptions } from './playwright-declarations'; import type { E2EPage, E2ESkip, BrowserNameOrCallback, SetIonViewportOptions } from './playwright-declarations';
type CustomTestArgs = PlaywrightTestArgs & type CustomTestArgs = PlaywrightTestArgs &
PlaywrightTestOptions & PlaywrightTestOptions &
@ -29,6 +29,7 @@ type CustomTestArgs = PlaywrightTestArgs &
type CustomFixtures = { type CustomFixtures = {
page: E2EPage; page: E2EPage;
skip: E2ESkip;
}; };
/** /**
@ -63,4 +64,25 @@ export const test = base.extend<CustomFixtures>({
page = await extendPageFixture(page, testInfo); page = await extendPageFixture(page, testInfo);
await use(page); await use(page);
}, },
skip: {
rtl: (reason = 'The functionality that is being tested is not applicable to RTL layouts.') => {
const testInfo: TestInfo = base.info();
base.skip(testInfo.project.metadata.rtl === true, reason);
},
browser: (
browserNameOrFunction: BrowserNameOrCallback,
reason = `The functionality that is being tested is not applicable to this browser.`
) => {
const browserName = base.info().project.use.browserName!;
if (typeof browserNameOrFunction === 'function') {
base.skip(browserNameOrFunction(browserName), reason);
} else {
base.skip(browserName === browserNameOrFunction, reason);
}
},
mode: (mode: string, reason = `The functionality that is being tested is not applicable to ${mode} mode`) => {
base.skip(base.info().project.metadata.mode === mode, reason);
},
},
}); });