mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
test(many): gestures flakiness (#27808)
Issue number: multiple internals --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Multiple tests that use gestures are flaky on GitHub. Due to that those tests are being skipped. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - `page.mouse.move` will not work as expected if it the mouse moves outside of the viewport. This may lead to events to not fire every time. There's now a check to determine if the coordinates are valid. If they are not, then it will update the coordinates to be as close to the viewport's edge instead of being outside. - Safari doesn't repaint the frame as often as the other browsers. This causes the tests on GitHub to appear to be lagging. Now the frame is forced to repaint only for Safari. - Most tests are no longer being skipped. - Range is still having issues on GitHub. It is no longer flaky locally with the changes in this PR. I've had to revert them back to skip until further notice. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> If this PR is merged, then: - FW-3006, FW-2795, and FW-3079 can be closed - FW-4556 still needs to remain open since range is still flaky on GitHub --------- Co-authored-by: ionitron <hi@ionicframework.com> Co-authored-by: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com>
This commit is contained in:
@ -3,7 +3,6 @@ import { configs, dragElementBy, test } from '@utils/test/playwright';
|
|||||||
|
|
||||||
import { testSlidingItem } from '../test.utils';
|
import { testSlidingItem } from '../test.utils';
|
||||||
|
|
||||||
// TODO FW-3006
|
|
||||||
/**
|
/**
|
||||||
* item-sliding doesn't have mode-specific styling
|
* item-sliding doesn't have mode-specific styling
|
||||||
*/
|
*/
|
||||||
@ -18,19 +17,12 @@ configs({ modes: ['md'] }).forEach(({ title, screenshot, config }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO FW-3006
|
|
||||||
/**
|
/**
|
||||||
* This behavior does not vary across modes/directions
|
* This behavior does not vary across modes/directions
|
||||||
*/
|
*/
|
||||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||||
test.describe(title('item-sliding: basic'), () => {
|
test.describe(title('item-sliding: basic'), () => {
|
||||||
// mouse gesture is flaky on CI, skip for now
|
test('should open when swiped', async ({ page }) => {
|
||||||
test.fixme('should open when swiped', async ({ page, skip }) => {
|
|
||||||
skip.browser(
|
|
||||||
(browserName: string) => browserName !== 'chromium',
|
|
||||||
'dragElementBy is flaky outside of Chrome browsers.'
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||||
const item = page.locator('#item2');
|
const item = page.locator('#item2');
|
||||||
|
|
||||||
@ -41,7 +33,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
|
|||||||
await expect(item).toHaveScreenshot(screenshot(`item-sliding-gesture`));
|
await expect(item).toHaveScreenshot(screenshot(`item-sliding-gesture`));
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
test('should not scroll when the item-sliding is swiped', async ({ page, skip }) => {
|
||||||
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
skip.browser('webkit', 'mouse.wheel is not available in WebKit');
|
||||||
|
|
||||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
@ -73,7 +73,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
await ionModalDidPresent.next();
|
await ionModalDidPresent.next();
|
||||||
|
|
||||||
const modalHeader = page.locator('#modal-header');
|
const modalHeader = page.locator('#modal-header');
|
||||||
await dragElementBy(modalHeader, page, 0, -500);
|
await dragElementBy(modalHeader, page, 0, 30);
|
||||||
|
|
||||||
const modal = page.locator('ion-modal');
|
const modal = page.locator('ion-modal');
|
||||||
expect(modal).not.toBe(null);
|
expect(modal).not.toBe(null);
|
||||||
|
@ -9,12 +9,7 @@ import { CardModalPage } from '../fixtures';
|
|||||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe(title('card modal - nav'), () => {
|
test.describe(title('card modal - nav'), () => {
|
||||||
let cardModalPage: CardModalPage;
|
let cardModalPage: CardModalPage;
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
skip.browser(
|
|
||||||
(browserName: string) => 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', config);
|
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false', config);
|
||||||
});
|
});
|
||||||
@ -33,7 +28,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
const content = page.locator('.page-two-content');
|
const content = page.locator('.page-two-content');
|
||||||
|
|
||||||
await dragElementBy(content, page, 1000, 0, 10);
|
await dragElementBy(content, page, 370, 0, 10);
|
||||||
|
|
||||||
await ionNavDidChange.next();
|
await ionNavDidChange.next();
|
||||||
});
|
});
|
||||||
@ -47,7 +42,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
await ionNavDidChange.next();
|
await ionNavDidChange.next();
|
||||||
|
|
||||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
|
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content', true, 270);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,7 +15,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
const modal = page.locator('ion-modal');
|
const modal = page.locator('ion-modal');
|
||||||
const content = (await page.$('ion-modal ion-content'))!;
|
const content = (await page.$('ion-modal ion-content'))!;
|
||||||
|
|
||||||
await dragElementBy(content, page, 0, 500);
|
await dragElementBy(content, page, 0, 300);
|
||||||
|
|
||||||
await content.waitForElementState('stable');
|
await content.waitForElementState('stable');
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
await content.evaluate((el: HTMLElement) => (el.scrollTop = 500));
|
await content.evaluate((el: HTMLElement) => (el.scrollTop = 500));
|
||||||
|
|
||||||
await dragElementBy(content, page, 0, 500);
|
await dragElementBy(content, page, 0, 300);
|
||||||
|
|
||||||
await content.waitForElementState('stable');
|
await content.waitForElementState('stable');
|
||||||
|
|
||||||
|
@ -61,12 +61,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
test('it should not swipe to close when swiped on the content but the content is scrolled', async ({ page }) => {
|
test('it should not swipe to close when swiped on the content but the content is scrolled', async ({ page }) => {
|
||||||
const modal = await cardModalPage.openModalByTrigger('#card');
|
const modal = await cardModalPage.openModalByTrigger('#card');
|
||||||
|
|
||||||
const content = (await page.$('ion-modal ion-content'))!;
|
const content = page.locator('ion-modal ion-content');
|
||||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||||
|
|
||||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||||
|
|
||||||
await content.waitForElementState('stable');
|
await content.waitFor();
|
||||||
await expect(modal).toBeVisible();
|
await expect(modal).toBeVisible();
|
||||||
});
|
});
|
||||||
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
||||||
@ -76,12 +76,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
await page.click('ion-button.replace');
|
await page.click('ion-button.replace');
|
||||||
|
|
||||||
const content = (await page.$('ion-modal ion-content'))!;
|
const content = page.locator('ion-modal ion-content');
|
||||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||||
|
|
||||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||||
|
|
||||||
await content.waitForElementState('stable');
|
await content.waitFor();
|
||||||
await expect(modal).toBeVisible();
|
await expect(modal).toBeVisible();
|
||||||
});
|
});
|
||||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||||
|
@ -16,7 +16,7 @@ export class CardModalPage {
|
|||||||
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||||
}
|
}
|
||||||
async openModalByTrigger(selector: string) {
|
async openModalByTrigger(selector: string) {
|
||||||
await this.page.click(selector);
|
await this.page.locator(selector).click();
|
||||||
await this.ionModalDidPresent.next();
|
await this.ionModalDidPresent.next();
|
||||||
|
|
||||||
return this.page.locator('ion-modal');
|
return this.page.locator('ion-modal');
|
||||||
|
@ -129,7 +129,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange');
|
const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange');
|
||||||
const header = page.locator('.modal-sheet ion-header');
|
const header = page.locator('.modal-sheet ion-header');
|
||||||
|
|
||||||
await dragElementBy(header, page, 0, 150);
|
await dragElementBy(header, page, 0, 125);
|
||||||
|
|
||||||
await ionBreakpointDidChange.next();
|
await ionBreakpointDidChange.next();
|
||||||
|
|
||||||
|
@ -77,13 +77,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||||
|
|
||||||
const box = (await knobEl.boundingBox())!;
|
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||||
const centerX = box.x + box.width / 2;
|
|
||||||
const centerY = box.y + box.height / 2;
|
|
||||||
|
|
||||||
await page.mouse.move(centerX, centerY);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(centerX + 30, centerY);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not use scrollToBottom() or other scrolling methods
|
* Do not use scrollToBottom() or other scrolling methods
|
||||||
|
@ -10,15 +10,28 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
/**
|
/**
|
||||||
* The mouse events are flaky on CI
|
* The mouse events are flaky on CI
|
||||||
*/
|
*/
|
||||||
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
|
test.fixme('should emit start/end events', async ({ page }) => {
|
||||||
await page.setContent(`<ion-range value="20"></ion-range>`, config);
|
/**
|
||||||
|
* Requires padding to prevent the knob from being clipped.
|
||||||
|
* If it's clipped, then the value might be one off.
|
||||||
|
* For example, if the knob is clipped on the right, then the value
|
||||||
|
* will be 99 instead of 100.
|
||||||
|
*/
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<div style="padding: 0 20px">
|
||||||
|
<ion-range value="20"></ion-range>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
||||||
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||||
|
|
||||||
const rangeEl = page.locator('ion-range');
|
const rangeEl = page.locator('ion-range');
|
||||||
|
|
||||||
await dragElementBy(rangeEl, page, testInfo.project.metadata.rtl ? -300 : 300, 0);
|
await dragElementBy(rangeEl, page, 300, 0);
|
||||||
await page.waitForChanges();
|
await page.waitForChanges();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,13 +78,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
|
|
||||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||||
|
|
||||||
const box = (await knobEl.boundingBox())!;
|
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||||
const centerX = box.x + box.width / 2;
|
|
||||||
const centerY = box.y + box.height / 2;
|
|
||||||
|
|
||||||
await page.mouse.move(centerX, centerY);
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(centerX + 30, centerY);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not use scrollToBottom() or other scrolling methods
|
* Do not use scrollToBottom() or other scrolling methods
|
||||||
@ -118,13 +125,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||||
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
const ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||||
|
|
||||||
const boundingBox = await rangeHandle.boundingBox();
|
await dragElementBy(rangeHandle, page, 100, 0);
|
||||||
|
|
||||||
await rangeHandle.hover();
|
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
|
||||||
|
|
||||||
await page.mouse.up();
|
|
||||||
|
|
||||||
await ionChangeSpy.next();
|
await ionChangeSpy.next();
|
||||||
|
|
||||||
@ -169,11 +170,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
|||||||
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
const rangeHandle = page.locator('ion-range .range-knob-handle');
|
||||||
const ionInputSpy = await page.spyOnEvent('ionInput');
|
const ionInputSpy = await page.spyOnEvent('ionInput');
|
||||||
|
|
||||||
const boundingBox = await rangeHandle.boundingBox();
|
|
||||||
|
|
||||||
await rangeHandle.hover();
|
await rangeHandle.hover();
|
||||||
await page.mouse.down();
|
|
||||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
await dragElementBy(rangeHandle, page, 100, 0, undefined, undefined, false);
|
||||||
|
|
||||||
await ionInputSpy.next();
|
await ionInputSpy.next();
|
||||||
|
|
||||||
|
@ -3,13 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
|||||||
|
|
||||||
import { pullToRefresh } from '../test.utils';
|
import { pullToRefresh } from '../test.utils';
|
||||||
|
|
||||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This behavior does not vary across directions.
|
* This behavior does not vary across directions.
|
||||||
*/
|
*/
|
||||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe.skip(title('refresher: basic'), () => {
|
test.describe(title('refresher: basic'), () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/src/components/refresher/test/basic', config);
|
await page.goto('/src/components/refresher/test/basic', config);
|
||||||
});
|
});
|
||||||
|
@ -3,12 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
|||||||
|
|
||||||
import { pullToRefresh } from '../test.utils';
|
import { pullToRefresh } from '../test.utils';
|
||||||
|
|
||||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
|
||||||
/**
|
/**
|
||||||
* This behavior does not vary across directions.
|
* This behavior does not vary across directions.
|
||||||
*/
|
*/
|
||||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe.skip(title('refresher: custom scroll target'), () => {
|
test.describe(title('refresher: custom scroll target'), () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/src/components/refresher/test/scroll-target', config);
|
await page.goto('/src/components/refresher/test/scroll-target', config);
|
||||||
});
|
});
|
||||||
@ -19,7 +18,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
|||||||
|
|
||||||
expect(await items.count()).toBe(30);
|
expect(await items.count()).toBe(30);
|
||||||
|
|
||||||
await pullToRefresh(page, '#inner-scroll');
|
await pullToRefresh(page);
|
||||||
|
|
||||||
expect(await items.count()).toBe(60);
|
expect(await items.count()).toBe(60);
|
||||||
});
|
});
|
||||||
@ -39,7 +38,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
|||||||
|
|
||||||
expect(await items.count()).toBe(30);
|
expect(await items.count()).toBe(30);
|
||||||
|
|
||||||
await pullToRefresh(page, '#inner-scroll');
|
await pullToRefresh(page);
|
||||||
|
|
||||||
expect(await items.count()).toBe(60);
|
expect(await items.count()).toBe(60);
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
|||||||
|
|
||||||
const ev = await page.spyOnEvent('ionRefreshComplete');
|
const ev = await page.spyOnEvent('ionRefreshComplete');
|
||||||
|
|
||||||
await dragElementByYAxis(target, page, 400);
|
await dragElementByYAxis(target, page, 320);
|
||||||
await ev.next();
|
await ev.next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||||
|
|
||||||
// TODO FW-3079
|
|
||||||
/**
|
/**
|
||||||
* Reorder group does not have per-mode styles
|
* Reorder group does not have per-mode styles
|
||||||
*/
|
*/
|
||||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe.skip(title('reorder group: interactive'), () => {
|
test.describe(title('reorder group: interactive'), () => {
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
skip.browser(
|
|
||||||
(browserName: string) => browserName !== 'chromium',
|
|
||||||
'dragElementBy is flaky outside of Chrome browsers.'
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.goto(`/src/components/reorder-group/test/interactive`, config);
|
await page.goto(`/src/components/reorder-group/test/interactive`, config);
|
||||||
});
|
});
|
||||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||||
|
|
||||||
// TODO FW-3079
|
|
||||||
/**
|
/**
|
||||||
* Reorder group does not have per-mode styles
|
* Reorder group does not have per-mode styles
|
||||||
*/
|
*/
|
||||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe.skip(title('reorder group: nested'), () => {
|
test.describe(title('reorder group: nested'), () => {
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
skip.browser(
|
|
||||||
(browserName: string) => browserName !== 'chromium',
|
|
||||||
'dragElementBy is flaky outside of Chrome browsers.'
|
|
||||||
);
|
|
||||||
await page.goto(`/src/components/reorder-group/test/nested`, config);
|
await page.goto(`/src/components/reorder-group/test/nested`, config);
|
||||||
});
|
});
|
||||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import { expect } from '@playwright/test';
|
import { expect } from '@playwright/test';
|
||||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||||
|
|
||||||
// TODO FW-3079
|
|
||||||
/**
|
/**
|
||||||
* Reorder group does not have per-mode styles
|
* Reorder group does not have per-mode styles
|
||||||
*/
|
*/
|
||||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
test.describe.skip(title('reorder group: scroll-target'), () => {
|
test.describe(title('reorder group: scroll-target'), () => {
|
||||||
test.beforeEach(async ({ page, skip }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
skip.browser(
|
|
||||||
(browserName: string) => browserName !== 'chromium',
|
|
||||||
'dragElementBy is flaky outside of Chrome browsers.'
|
|
||||||
);
|
|
||||||
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
|
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
|
||||||
});
|
});
|
||||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* The drag gesture will not operate as expected when the element is dragged outside of the viewport because the Mouse class does not fire events outside of the viewport.
|
||||||
|
*
|
||||||
|
* For example, if the mouse is moved outside of the viewport, then the `mouseup` event will not fire.
|
||||||
|
*
|
||||||
|
* See https://playwright.dev/docs/api/class-mouse#mouse-move for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
import type { ElementHandle, Locator } from '@playwright/test';
|
import type { ElementHandle, Locator } from '@playwright/test';
|
||||||
|
|
||||||
import type { E2EPage } from './';
|
import type { E2EPage } from './';
|
||||||
@ -8,7 +16,8 @@ export const dragElementBy = async (
|
|||||||
dragByX = 0,
|
dragByX = 0,
|
||||||
dragByY = 0,
|
dragByY = 0,
|
||||||
startXCoord?: number,
|
startXCoord?: number,
|
||||||
startYCoord?: number
|
startYCoord?: number,
|
||||||
|
releaseDrag = true
|
||||||
) => {
|
) => {
|
||||||
const boundingBox = await el.boundingBox();
|
const boundingBox = await el.boundingBox();
|
||||||
|
|
||||||
@ -21,14 +30,17 @@ export const dragElementBy = async (
|
|||||||
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
|
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
|
||||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||||
|
|
||||||
const endX = startX + dragByX;
|
// Navigate to the start position.
|
||||||
const endY = startY + dragByY;
|
|
||||||
|
|
||||||
await page.mouse.move(startX, startY);
|
await page.mouse.move(startX, startY);
|
||||||
|
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
|
|
||||||
await page.mouse.move(endX, endY, { steps: 10 });
|
// Drag the element.
|
||||||
|
await moveElement(page, startX, startY, dragByX, dragByY);
|
||||||
|
|
||||||
|
if (releaseDrag) {
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,12 +67,83 @@ export const dragElementByYAxis = async (
|
|||||||
const startX = boundingBox.x + boundingBox.width / 2;
|
const startX = boundingBox.x + boundingBox.width / 2;
|
||||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||||
|
|
||||||
|
// Navigate to the start position.
|
||||||
await page.mouse.move(startX, startY);
|
await page.mouse.move(startX, startY);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
|
|
||||||
for (let i = 0; i < dragByY; i += 20) {
|
// Drag the element.
|
||||||
await page.mouse.move(startX, startY + i);
|
await moveElement(page, startX, startY, 0, dragByY);
|
||||||
}
|
|
||||||
|
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateDragByX = (startX: number, dragByX: number, viewportWidth: number) => {
|
||||||
|
const endX = startX + dragByX;
|
||||||
|
// The element is being dragged past the right of the viewport.
|
||||||
|
if (endX > viewportWidth) {
|
||||||
|
const recommendedDragByX = viewportWidth - startX - 5;
|
||||||
|
throw new Error(
|
||||||
|
`The element is being dragged past the right of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The element is being dragged past the left of the viewport.
|
||||||
|
if (endX < 0) {
|
||||||
|
const recommendedDragByX = startX - 5;
|
||||||
|
throw new Error(
|
||||||
|
`The element is being dragged past the left of the viewport. Update the dragByX value to prevent going out of bounds. A recommended value is ${recommendedDragByX}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateDragByY = (startY: number, dragByY: number, viewportHeight: number) => {
|
||||||
|
const endY = startY + dragByY;
|
||||||
|
// The element is being dragged past the bottom of the viewport.
|
||||||
|
if (endY > viewportHeight) {
|
||||||
|
const recommendedDragByY = viewportHeight - startY - 5;
|
||||||
|
throw new Error(
|
||||||
|
`The element is being dragged past the bottom of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The element is being dragged past the top of the viewport.
|
||||||
|
if (endY < 0) {
|
||||||
|
const recommendedDragByY = startY - 5;
|
||||||
|
throw new Error(
|
||||||
|
`The element is being dragged past the top of the viewport. Update the dragByY value to prevent going out of bounds. A recommended value is ${recommendedDragByY}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveElement = async (page: E2EPage, startX: number, startY: number, dragByX = 0, dragByY = 0) => {
|
||||||
|
const steps = 10;
|
||||||
|
const browser = page.context().browser()!.browserType().name();
|
||||||
|
|
||||||
|
const viewport = page.viewportSize();
|
||||||
|
if (viewport === null) {
|
||||||
|
throw new Error(
|
||||||
|
'Cannot get viewport size. See https://playwright.dev/docs/api/class-page#page-viewport-size for more information'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDragByX(startX, dragByX, viewport.width);
|
||||||
|
validateDragByY(startY, dragByY, viewport.height);
|
||||||
|
|
||||||
|
const endX = startX + dragByX;
|
||||||
|
const endY = startY + dragByY;
|
||||||
|
|
||||||
|
// Drag the element.
|
||||||
|
for (let i = 1; i <= steps; i++) {
|
||||||
|
const middleX = startX + (endX - startX) * (i / steps);
|
||||||
|
const middleY = startY + (endY - startY) * (i / steps);
|
||||||
|
|
||||||
|
await page.mouse.move(middleX, middleY);
|
||||||
|
|
||||||
|
// Safari needs to wait for a repaint to occur before moving the mouse again.
|
||||||
|
if (browser === 'webkit' && i % 2 === 0) {
|
||||||
|
// Repainting every 2 steps is enough to keep the drag gesture smooth.
|
||||||
|
// Anything past 4 steps will cause the drag gesture to be flaky.
|
||||||
|
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user