mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 18:54:11 +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';
|
||||
|
||||
// TODO FW-3006
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('item-sliding: basic'), () => {
|
||||
// mouse gesture is flaky on CI, skip for now
|
||||
test.fixme('should open when swiped', async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test('should open when swiped', async ({ page }) => {
|
||||
await page.goto(`/src/components/item-sliding/test/basic`, config);
|
||||
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`));
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
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();
|
||||
|
||||
const modalHeader = page.locator('#modal-header');
|
||||
await dragElementBy(modalHeader, page, 0, -500);
|
||||
await dragElementBy(modalHeader, page, 0, 30);
|
||||
|
||||
const modal = page.locator('ion-modal');
|
||||
expect(modal).not.toBe(null);
|
||||
|
@ -9,12 +9,7 @@ import { CardModalPage } from '../fixtures';
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('card modal - nav'), () => {
|
||||
let cardModalPage: CardModalPage;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
cardModalPage = new CardModalPage(page);
|
||||
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');
|
||||
|
||||
await dragElementBy(content, page, 1000, 0, 10);
|
||||
await dragElementBy(content, page, 370, 0, 10);
|
||||
|
||||
await ionNavDidChange.next();
|
||||
});
|
||||
@ -47,7 +42,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
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 content = (await page.$('ion-modal ion-content'))!;
|
||||
|
||||
await dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
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 dragElementBy(content, page, 0, 500);
|
||||
await dragElementBy(content, page, 0, 300);
|
||||
|
||||
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 }) => {
|
||||
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 cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
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 ({
|
||||
@ -76,12 +76,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
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 cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
await content.waitFor();
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||
|
@ -16,7 +16,7 @@ export class CardModalPage {
|
||||
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
|
||||
}
|
||||
async openModalByTrigger(selector: string) {
|
||||
await this.page.click(selector);
|
||||
await this.page.locator(selector).click();
|
||||
await this.ionModalDidPresent.next();
|
||||
|
||||
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 header = page.locator('.modal-sheet ion-header');
|
||||
|
||||
await dragElementBy(header, page, 0, 150);
|
||||
await dragElementBy(header, page, 0, 125);
|
||||
|
||||
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);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
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);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
test.fixme('should emit start/end events', async ({ page }, testInfo) => {
|
||||
await page.setContent(`<ion-range value="20"></ion-range>`, config);
|
||||
test.fixme('should emit start/end events', async ({ page }) => {
|
||||
/**
|
||||
* 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 rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||
|
||||
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();
|
||||
|
||||
/**
|
||||
@ -65,13 +78,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
|
||||
expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0);
|
||||
|
||||
const box = (await knobEl.boundingBox())!;
|
||||
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);
|
||||
await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false);
|
||||
|
||||
/**
|
||||
* 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 ionChangeSpy = await page.spyOnEvent('ionChange');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
await rangeHandle.hover();
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(boundingBox!.x + 100, boundingBox!.y);
|
||||
|
||||
await page.mouse.up();
|
||||
await dragElementBy(rangeHandle, page, 100, 0);
|
||||
|
||||
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 ionInputSpy = await page.spyOnEvent('ionInput');
|
||||
|
||||
const boundingBox = await rangeHandle.boundingBox();
|
||||
|
||||
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();
|
||||
|
||||
|
@ -3,13 +3,11 @@ import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
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.
|
||||
*/
|
||||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('refresher: basic'), () => {
|
||||
test.describe(title('refresher: basic'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
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';
|
||||
|
||||
// TODO FW-2795: Enable this test when touch events/gestures are better supported in Playwright
|
||||
/**
|
||||
* This behavior does not vary across directions.
|
||||
*/
|
||||
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 }) => {
|
||||
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);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
@ -39,7 +38,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
|
||||
expect(await items.count()).toBe(30);
|
||||
|
||||
await pullToRefresh(page, '#inner-scroll');
|
||||
await pullToRefresh(page);
|
||||
|
||||
expect(await items.count()).toBe(60);
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
||||
|
||||
const ev = await page.spyOnEvent('ionRefreshComplete');
|
||||
|
||||
await dragElementByYAxis(target, page, 400);
|
||||
await dragElementByYAxis(target, page, 320);
|
||||
await ev.next();
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
|
||||
test.describe(title('reorder group: interactive'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/interactive`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: nested'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/nested`, config);
|
||||
});
|
||||
test('should drag and drop when ion-reorder wraps ion-item', async ({ page }) => {
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test, dragElementBy } from '@utils/test/playwright';
|
||||
|
||||
// TODO FW-3079
|
||||
/**
|
||||
* Reorder group does not have per-mode styles
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe.skip(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.browser(
|
||||
(browserName: string) => browserName !== 'chromium',
|
||||
'dragElementBy is flaky outside of Chrome browsers.'
|
||||
);
|
||||
test.describe(title('reorder group: scroll-target'), () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`/src/components/reorder-group/test/scroll-target`, config);
|
||||
});
|
||||
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 { E2EPage } from './';
|
||||
@ -8,7 +16,8 @@ export const dragElementBy = async (
|
||||
dragByX = 0,
|
||||
dragByY = 0,
|
||||
startXCoord?: number,
|
||||
startYCoord?: number
|
||||
startYCoord?: number,
|
||||
releaseDrag = true
|
||||
) => {
|
||||
const boundingBox = await el.boundingBox();
|
||||
|
||||
@ -21,14 +30,17 @@ export const dragElementBy = async (
|
||||
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
const endX = startX + dragByX;
|
||||
const endY = startY + dragByY;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -55,12 +67,83 @@ export const dragElementByYAxis = async (
|
||||
const startX = boundingBox.x + boundingBox.width / 2;
|
||||
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
|
||||
|
||||
// Navigate to the start position.
|
||||
await page.mouse.move(startX, startY);
|
||||
await page.mouse.down();
|
||||
|
||||
for (let i = 0; i < dragByY; i += 20) {
|
||||
await page.mouse.move(startX, startY + i);
|
||||
}
|
||||
// Drag the element.
|
||||
await moveElement(page, startX, startY, 0, dragByY);
|
||||
|
||||
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