From f07a5e028e9904294807cbe68a0909851417eb19 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 19:28:37 -0700 Subject: [PATCH 01/11] test(dragElementBy, item-sliding): prevent full swipe --- .../test/basic/item-sliding.e2e.ts | 30 +++++++++++------- .../test/full-swipe/item-sliding.e2e.ts | 10 +++--- .../test/icons/item-sliding.e2e.ts | 2 +- .../test/shapes/item-sliding.e2e.ts | 15 +++++---- ...ionic-md-ltr-light-Mobile-Safari-linux.png | Bin 1867 -> 5310 bytes ...ionic-md-ltr-light-Mobile-Safari-linux.png | Bin 1833 -> 5132 bytes .../src/utils/test/playwright/drag-element.ts | 3 -- 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index fa784af4090..ae7ea2301b0 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -1,6 +1,13 @@ import { expect } from '@playwright/test'; import { configs, dragElementBy, test } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe threshold + * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. + */ +const DRAG_DISTANCE_ONE_OPTION = 100; +const DRAG_DISTANCE_TWO_OPTIONS = 150; + /** * item-sliding doesn't have mode-specific styling, * but the child components, item-options and item-option, do. @@ -8,7 +15,7 @@ import { configs, dragElementBy, test } from '@utils/test/playwright'; * It is important to test all modes to ensure that the * child components are being rendered correctly. */ -configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => { +configs().forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: basic'), () => { test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/basic`, config); @@ -23,10 +30,9 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? -100 : 100; + const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_ONE_OPTION : DRAG_DISTANCE_ONE_OPTION; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 20); - await page.waitForChanges(); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await expect(item).toHaveScreenshot(screenshot('item-sliding-start')); }); @@ -42,9 +48,9 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? 150 : -150; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 20); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await expect(item).toHaveScreenshot(screenshot('item-sliding-end')); }); @@ -61,7 +67,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co await page.goto(`/src/components/item-sliding/test/basic`, config); const item = page.locator('#item2'); - await dragElementBy(item, page, -150, 0, undefined, undefined, true, 20); + await dragElementBy(item, page, -DRAG_DISTANCE_TWO_OPTIONS, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); // item-sliding doesn't have an easy way to tell whether it's fully open so just screenshot it @@ -105,7 +111,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co * make sure the safe area padding is applied only to that side * regardless of direction */ -configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => { +configs().forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: basic'), () => { test.describe('safe area left', () => { test('should have padding on the left only', async ({ page }) => { @@ -140,8 +146,8 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? -150 : 150; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 20); + const dragByX = direction == 'rtl' ? -DRAG_DISTANCE_TWO_OPTIONS : DRAG_DISTANCE_TWO_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-left`)); @@ -181,8 +187,8 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? 150 : -150; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 20); + const dragByX = direction == 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-right`)); diff --git a/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts b/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts index 1ba692bf5a3..38d09885389 100644 --- a/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts @@ -27,7 +27,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr', 'rtl'] }).forEac Delete - `, + `, config ); @@ -52,7 +52,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr', 'rtl'] }).forEac Archive - `, + `, config ); @@ -77,7 +77,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr', 'rtl'] }).forEac Delete - `, + `, config ); @@ -132,7 +132,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr', 'rtl'] }).forEac Edit - `, + `, config ); @@ -166,7 +166,7 @@ configs({ modes: ['md'], directions: ['ltr', 'rtl'] }).forEach(({ title, config Delete - `, + `, config ); diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index f7bc0dc7e9a..1f35fa3eec2 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -25,7 +25,7 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf */ const dragByX = config.direction === 'rtl' ? 150 : -150; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 20); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); // Convert camelCase ids to kebab-case for screenshot file names diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index 6010e8e5266..5a6a962947d 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -7,12 +7,13 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; */ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: shapes'), () => { - test('should not have visual regressions when not expanded', async ({ page }) => { + test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/shapes`, config); + }); - const itemIDs = ['round', 'soft', 'rectangular']; - for (const itemID of itemIDs) { - const item = page.locator(`#${itemID}`); + ['round', 'soft', 'rectangular'].forEach((shape) => { + test(`${shape} - should not have visual regressions when not expanded`, async ({ page }) => { + const item = page.locator(`#${shape}`); /** * Negative dragByX value to drag element from the right to the left @@ -20,11 +21,11 @@ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screensh */ const dragByX = -150; - await dragElementBy(item, page, dragByX); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); - await expect(item).toHaveScreenshot(screenshot(`item-sliding-${itemID}`)); - } + await expect(item).toHaveScreenshot(screenshot(`item-sliding-${shape}`)); + }); }); }); }); diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Safari-linux.png index 3bc0ee848bc5158062c1de72594422e08590ff93..91cc083d380f60a10d84ab94b201e96589655ec1 100644 GIT binary patch delta 5294 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 delta 1824 zcmY+Fc{tSj9>-^prG`nCA$vyB92r?BlPqJIQR&b^W6y3vjID`r5F*P=awjb!>xT&D8z9oLfR&L^lma@N)M$gT>g@e;wC)qs!O zB4BE!lkTGm?T^92^7tFSJvr;u|J+1g^1euV$dN0JS<-j4vS{oT^Xg!InIwBWZkMOF z-#2KW{&RPJk2J}Orl||JBgq&loqb|>M*xnEPIb?Ot05s&y1=4dSam?O7?mC<%$JN& zKCh{)Gqx=uP}<_UqaNW;r3a`$RHWk_ewibXSZqvu4PQu&D#lJYp`HpaRq{8Iu!}hN zSt_N>T?SrSaM;|}H)U`jJnRmT`crRR?H{O6{cS@TLF{qR@jUWuZGp`hu|!j@2E#CLR0^X-z$rzkJYKD*RP{oQ;RI9rTEK!^CF;)4t}g zw7YRYl%kzIIlaWy=tBj6T7v1AT87A5nc0tQ?&L#ph(dn|rrC*ID$3qe)9NCYXpc4Jyr ztrrQD_BEEX4Wx5!xm!mEOpPdY9JB{0vg2!4#^T&$;8+nzJY3_q{~8Ji%WD01&=R<< z$osqyl1DkaaDi-R>FPr78M>xcDwM#h%MQQpo#1tW3*lTk4RU+ecX0uI&0@Ea`48b1 zZJ;bdp4wKhaD!+8cH1ex&p=kj?J<}fUgLpyV*4uLx@LZB!ukYndpxT;icuTP=cN{3 z(U#*(I|x9&F-#RI$zEk0l6okg8#OxnA?1|=xiJ_Gy{PGpaj%8-uC7n~(~7zii-d^W z^~pxc$ZtIcC@FIK-K_M!k zN{SXyep9)f_v-SePhvG9S@D9*?UBHhmgZ!k5^3^a-#}aC4W;LWsy$^kCn*Nsc(x~+ zvMY4kx`QCptT^&_v}kKD)1{|RMHb}gW6AB**&TUZ@_?j1&tLV^7csu23r0o}6b;h4 zPAVo1BbHBTB%cM|pgK|3$eR*MN{zgwl#h7g7001JldNuPXe5W+urNkBHivSifC=Ns zZL2SOHZ!ib*bnM4k+*Rw;{}Xz#9^&w=k|?lBZtN{IQbr1fTK0ihQRwjz)Zidzl@C7 z{;Pt6Zz)e7wY@5y5%Cw33l+*~^n{g78h2(COm>*3@+_iltSX)I0NW+edyU76FKKu# zrnFC-FQIl^moNVPjo!=?&Xyi~UzV)uu*qI~>8wl1>zG^LZBgK#Mns6swpQzl$p`Se zwQt!^gv`2qz`Yt_G*MWShCS4vsn#*qi_==E>LII|qN?mjK#HYl_S5&$!{de4DyhhKb0j|?D&y=CAUhpG()S`}smLpvJU zx}KtPEqWE)MsxjFXis|S+EIJn-QqF3I6deW`?ue|Ui`Xtq<2Shu892}pE6RlFWH`m z5i#fP(ak`)V;-QF6Apu6_sDl*YKH}DMdRT`(=^oMtT`*kW?w!~RQv#U$tP)Tec@*M z(Cg6IRl|%Hr|yVisLy0qKj^#hC*18@=EJ0}hUR2o2o#DUGHmataahcS#QyhBXJIxi zM%gt3zkt5pN!}RfCU+ckKE*ZDFoNo{%V5x7fn`{57Dd;soFdCEf~CAr^pZoK7OIT; z${Y?spOXb_>Yuuye!Q_)_1?op2TOgCqFd;Mwq8<&C@y!dF2xHP({;6rm7VY`ar zXcJK#6W|Ptn|)Ac&5*ImnMJdgtmKV`P=Uz6t72@Q^o5EP6%$tMI%9sfG-%PyBkD}i z{a;-w(d73{8B@6a=^iU5mgZFN(j`5DWB80->pA+k{~WGOc3VB$M7z){J$;&2wTTF>(6>VtHxf6=IZD+}%{1u%)< z`Yhf?4nVF6HEM1MX*~d<(!%E}A&F!FyqUg<0H~bdI{pB-SYDR=fL~bH+&r8^36g>P zfn@UKuy@6cfi(M0Jv^%3i48B&MaRykG z#dEMeR3S=JQ(0BvMZd7@7cLR{7D)`dhP8tU6{!6Okg~v+!-bwo*>#aO#dd;#gSEJf JsW5Yk`y27_YfS(E diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Safari-linux.png index d5c999183a4b6f52be6cfbd07e00194c594a401e..f0ecfd908fd431e933124337ce2539c0e509a228 100644 GIT binary patch literal 5132 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 delta 1789 zcmZ`(c|6qn8lFKEGu*OG<5+CW~_~@W6saL|J?hzpZopu{e0i|ectzdo+n+6Do#2F2>*pQ#W@lw zi(doNb3Vy;t}bh1qdhe%WQV^Rdqy5UK^G7}YD(MCs+DuEo<8OExId8b{LoEfh5)>z z+LSSomg4fv>4@BG+JQPhs(s18KhLSFi%N6C;1yw)BSM2YVflrnn#}T87q$0OuoFAH z#)5opyWa0UafDvJ9!tm*K%`ibxp#(hm*P=W;1mLdKP;SxLaU4oe~B&GQNnK@JOFL0 zQc{rk(4CPb|F(MB-HU*?2V_%1G@z^_%5J3%bVaEPc~7>5vK;W_@LV=WRPQsxMy36b zN+OOd9B-;aG0Z+srM*+a3!3^m=P_Qq^B@&Ni`m?E3pm&bfGgc=Yu9POAW;60`H16q zGWQ7?&B^q|Sdvv>!g%{6F!O$hH4JZ6g-H^VkChaH;z*O?Zu@b6#&B3>r~BE~Uf#_l z95ZTE$Wv!J;QnSW_b^PJQaGtl0@lu0uuuME}(!rl|aREeOm6)kbFPbub*-CoYyBBJh zxep42{O@J8dXP|N2pGBeAULv==YJswKcV3X-)XnSJ;nq-^+Z2?yw`{?Y$&s`M<(%K zKZ*dZ2Bnvb??^@3s6##4`mgvH)f7~*w%tv2y-3BUPso!$D$aiTks{DiEoU9&6LhlO zjJM3M+n+uB<4VjW4MgAO<5vFGjkP?LB3De&!hM|cQ(svhjHNx&F_#s(oFlrY`}Sd$ z2|;tXyk7r-v{*-IUrv~L&g`{{yRx%51Asgzk>qJf`d!=5;DrU*P*_V^l=}ZCV_aW2 zn?1vE;?4kk*R+#(MM-we>8fxW!D!^!LjrFC?a_oKMKhc0d>48Q^~!`QK6a%8ON-(C z?)D{?aCE~KUApd=;9h?Ayh_$gd%GkW32^zcS#Ee)*NNFw9C>>p7&|61am=0OIY+ty zStEGw&fROeK?`Q}-fM?&3>b9gb_7H<13gurwS~%F33Jny{zdOR(n08)16!|Vu=b*> zTg!_z{nmdhMAWV>P1+u}3VTDV$xCSL>DhDjCKVeZt}?6m121)u(QGk?V~;9<#ess` zCX;nTa`|V1u_w+@CN}<1U@dKpMtKq>|3v8TjD*grj}ApQ-Eu9+U;hw9rWXyR#YM&+ zO^d8tTbJBjS#A1KJEvGpvAQ-h1~O$KamUEv-5|Y0{c>FDsPE8f|C$LWXfC}vDlk}N zywETq*GRteakAu_xNzmSyzYwtq&b|8`JuSz#UIdsFoUsl_>lPXdaVaG4I{*bTyOQg z*Fenz8cY?vI`1kvhy?4dya*e7_)eRxLGVs{!r>r($2 z1PFgA&&|Rt6c8_y$rO3&cR|K|kBcc9A8Z?VUUdblPk-O1vqJNKpO1Ye8t*28^$!SK zZ0g_^bmxIbsGNp6bigMi6)Bw;oW>m9{Zj#Ms?w4==QD+tL)7*q? zpF(j^q+CHn&D!wD&(}Hy1IolweGklhYa@ae2?myfecMyauYP8Nl;kvZ?256C(yxx@aY5-@c1zEK?Boi@Z+g#?3tx>gC|S`2`9#z@PU}$L?mEJ*aZ|y< z?#Cd!BlEb&So-mxGI;^6R1QmH)S{azBpDz|RsT5otv new Promise(requestAnimationFrame)); } } From 03a4a7729340c91a19f863f18ba1aced0d786a6a Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 19:32:53 -0700 Subject: [PATCH 02/11] test(drag-element): add comments back --- core/src/utils/test/playwright/drag-element.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index 79ee1eb5f95..3ec6c9fe162 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -132,6 +132,7 @@ const moveElement = async (page: E2EPage, startX: number, startY: number, dragBy 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); @@ -140,6 +141,8 @@ const moveElement = async (page: E2EPage, startX: number, startY: number, dragBy // 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)); } } From fbd4983c2d4cbd1e22c4d9b451ef1fe64fc6fca4 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 19:45:32 -0700 Subject: [PATCH 03/11] test(item-sliding): break test down --- .../item-sliding/test/icons/item-sliding.e2e.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index 1f35fa3eec2..07f9f95dd26 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -10,12 +10,13 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; */ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: icons'), () => { - test('should not have visual regressions', async ({ page }) => { + test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/icons`, config); + }); - const itemIDs = ['iconsOnly', 'iconsStart', 'iconsEnd', 'iconsTop', 'iconsBottom']; - for (const itemID of itemIDs) { - const item = page.locator(`#${itemID}`); + ['iconsOnly', 'iconsStart', 'iconsEnd', 'iconsTop', 'iconsBottom'].forEach((position) => { + test(`${position} - should not have visual regressions`, async ({ page }) => { + const item = page.locator(`#${position}`); /** * Negative dragByX value to drag element from the right to the left @@ -29,9 +30,9 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf await page.waitForChanges(); // Convert camelCase ids to kebab-case for screenshot file names - const itemIDKebab = itemID.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - await expect(item).toHaveScreenshot(screenshot(`item-sliding-${itemIDKebab}`)); - } + const positionKebab = position.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + await expect(item).toHaveScreenshot(screenshot(`item-sliding-${positionKebab}`)); + }); }); }); }); From b1df2e2c2320d5ab0376c5adc1f0e1dfed0cd15d Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 19:50:02 -0700 Subject: [PATCH 04/11] test(item-sliding): use constant --- .../item-sliding/test/icons/item-sliding.e2e.ts | 8 +++++++- .../item-sliding/test/scroll-target/item-sliding.e2e.ts | 9 ++++++++- .../item-sliding/test/shapes/item-sliding.e2e.ts | 8 +++++++- .../item-sliding/test/states/item-sliding.e2e.ts | 8 +++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index 07f9f95dd26..d4ebdde8704 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -1,6 +1,12 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe threshold + * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. + */ +const DRAG_DISTANCE_TWO_OPTIONS = 150; + /** * item-sliding doesn't have mode-specific styling, * but the child components, item-options and item-option, do. @@ -24,7 +30,7 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? 150 : -150; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); diff --git a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts index 53772e7f02a..a99c6ebc845 100644 --- a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts @@ -1,5 +1,12 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; + +/** + * Drag distances that reveal options without crossing the full swipe threshold + * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. + */ +const DRAG_DISTANCE_TWO_OPTIONS = 150; + /** * This behavior does not vary across modes/directions */ @@ -15,7 +22,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - await dragElementBy(itemSlidingEl, page, -150, 0, undefined, undefined, false); + await dragElementBy(itemSlidingEl, page, -DRAG_DISTANCE_TWO_OPTIONS, 0, undefined, undefined, false); /** * Do not use scrollToBottom() or other scrolling methods diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index 5a6a962947d..c7a430da62a 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -1,6 +1,12 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe threshold + * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. + */ +const DRAG_DISTANCE_TWO_OPTIONS = 150; + /** * The shapes on the `item-option` do not vary by direction * when they are not being dragged. @@ -19,7 +25,7 @@ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screensh * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -150; + const dragByX = -DRAG_DISTANCE_TWO_OPTIONS; await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); await page.waitForChanges(); diff --git a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts index 01c40bf2110..c18c35df8fc 100644 --- a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts @@ -1,6 +1,12 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe threshold + * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. + */ +const DRAG_DISTANCE_TWO_OPTIONS = 150; + /** * This behavior does not vary across modes */ @@ -16,7 +22,7 @@ configs({ modes: ['ionic-md', 'md', 'ios'], directions: ['ltr'] }).forEach(({ ti * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -150; + const dragByX = -DRAG_DISTANCE_TWO_OPTIONS; await dragElementBy(item, page, dragByX); await page.waitForChanges(); From f6c4aeee685178295947328a01e6fafb86113014 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 20:02:41 -0700 Subject: [PATCH 05/11] test(item-sliding): cleanup --- .../test/basic/item-sliding.e2e.ts | 26 +++++++++---------- .../test/icons/item-sliding.e2e.ts | 6 ++--- .../test/scroll-target/item-sliding.e2e.ts | 4 +-- .../test/shapes/item-sliding.e2e.ts | 6 ++--- .../test/states/item-sliding.e2e.ts | 4 +-- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index ae7ea2301b0..4c99bbe82b2 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -5,8 +5,8 @@ import { configs, dragElementBy, test } from '@utils/test/playwright'; * Drag distances that reveal options without crossing the full swipe threshold * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. */ -const DRAG_DISTANCE_ONE_OPTION = 100; -const DRAG_DISTANCE_TWO_OPTIONS = 150; +const DRAG_DISTANCE_SINGLE_OPTION = 100; +const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; /** * item-sliding doesn't have mode-specific styling, @@ -15,7 +15,7 @@ const DRAG_DISTANCE_TWO_OPTIONS = 150; * It is important to test all modes to ensure that the * child components are being rendered correctly. */ -configs().forEach(({ title, screenshot, config }) => { +configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: basic'), () => { test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/basic`, config); @@ -30,9 +30,9 @@ configs().forEach(({ title, screenshot, config }) => { * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_ONE_OPTION : DRAG_DISTANCE_ONE_OPTION; + const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_SINGLE_OPTION : DRAG_DISTANCE_SINGLE_OPTION; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await expect(item).toHaveScreenshot(screenshot('item-sliding-start')); }); @@ -48,9 +48,9 @@ configs().forEach(({ title, screenshot, config }) => { * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await expect(item).toHaveScreenshot(screenshot('item-sliding-end')); }); @@ -67,7 +67,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co await page.goto(`/src/components/item-sliding/test/basic`, config); const item = page.locator('#item2'); - await dragElementBy(item, page, -DRAG_DISTANCE_TWO_OPTIONS, 0, undefined, undefined, undefined, 15); + await dragElementBy(item, page, -DRAG_DISTANCE_MULTIPLE_OPTIONS, 0, undefined, undefined, true, 15); await page.waitForChanges(); // item-sliding doesn't have an easy way to tell whether it's fully open so just screenshot it @@ -111,7 +111,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co * make sure the safe area padding is applied only to that side * regardless of direction */ -configs().forEach(({ title, screenshot, config }) => { +configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: basic'), () => { test.describe('safe area left', () => { test('should have padding on the left only', async ({ page }) => { @@ -146,8 +146,8 @@ configs().forEach(({ title, screenshot, config }) => { const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? -DRAG_DISTANCE_TWO_OPTIONS : DRAG_DISTANCE_TWO_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + const dragByX = direction == 'rtl' ? -DRAG_DISTANCE_MULTIPLE_OPTIONS : DRAG_DISTANCE_MULTIPLE_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-left`)); @@ -187,8 +187,8 @@ configs().forEach(({ title, screenshot, config }) => { const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + const dragByX = direction == 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-right`)); diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index d4ebdde8704..b249e35005e 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -5,7 +5,7 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; * Drag distances that reveal options without crossing the full swipe threshold * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. */ -const DRAG_DISTANCE_TWO_OPTIONS = 150; +const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; /** * item-sliding doesn't have mode-specific styling, @@ -30,9 +30,9 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_TWO_OPTIONS : -DRAG_DISTANCE_TWO_OPTIONS; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await page.waitForChanges(); // Convert camelCase ids to kebab-case for screenshot file names diff --git a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts index a99c6ebc845..12af9489d7e 100644 --- a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts @@ -5,7 +5,7 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; * Drag distances that reveal options without crossing the full swipe threshold * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. */ -const DRAG_DISTANCE_TWO_OPTIONS = 150; +const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; /** * This behavior does not vary across modes/directions @@ -22,7 +22,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - await dragElementBy(itemSlidingEl, page, -DRAG_DISTANCE_TWO_OPTIONS, 0, undefined, undefined, false); + await dragElementBy(itemSlidingEl, page, -DRAG_DISTANCE_MULTIPLE_OPTIONS, 0, undefined, undefined, false); /** * Do not use scrollToBottom() or other scrolling methods diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index c7a430da62a..f8396a8c82c 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -5,7 +5,7 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; * Drag distances that reveal options without crossing the full swipe threshold * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. */ -const DRAG_DISTANCE_TWO_OPTIONS = 150; +const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; /** * The shapes on the `item-option` do not vary by direction @@ -25,9 +25,9 @@ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screensh * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -DRAG_DISTANCE_TWO_OPTIONS; + const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, undefined, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-${shape}`)); diff --git a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts index c18c35df8fc..a273f2ab261 100644 --- a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts @@ -5,7 +5,7 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; * Drag distances that reveal options without crossing the full swipe threshold * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. */ -const DRAG_DISTANCE_TWO_OPTIONS = 150; +const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; /** * This behavior does not vary across modes @@ -22,7 +22,7 @@ configs({ modes: ['ionic-md', 'md', 'ios'], directions: ['ltr'] }).forEach(({ ti * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -DRAG_DISTANCE_TWO_OPTIONS; + const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; await dragElementBy(item, page, dragByX); await page.waitForChanges(); From b840229f6acab9f060efa0e5ee71ba4e8dedadcc Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 20:19:58 -0700 Subject: [PATCH 06/11] test(drag-element): update comments --- core/src/utils/test/playwright/drag-element.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index 3ec6c9fe162..d9cd7327611 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -139,10 +139,19 @@ const moveElement = async (page: E2EPage, startX: number, startY: number, dragBy await page.mouse.move(middleX, middleY); - // Safari needs to wait for a repaint to occur before moving the mouse again. + /** + * In Safari, gesture velocity is calculated relative to animation frames. + * Without waiting for a repaint, consecutive `mouse.move` events arrive + * with ~0ms time delta and velocity never accumulates, causing gesture + * detection to fail. + */ 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. + /** + * Repaintng every 2 steps is enough to keep the drag gesture smooth. + * Repainting on every step makes the test slow, and repainting every + * 4+ steps means Safari does not see enough frames to track the gesture + * reliably. + */ await page.evaluate(() => new Promise(requestAnimationFrame)); } } From c05509296f961c134b829b9eecef81e94b312097 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 27 Apr 2026 20:28:36 -0700 Subject: [PATCH 07/11] test(drag-element): add JSDocs to dragElementBy --- core/src/utils/test/playwright/drag-element.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index d9cd7327611..ea70e26aa7b 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -10,6 +10,18 @@ import type { ElementHandle, Locator } from '@playwright/test'; import type { E2EPage } from './'; +/** + * Drags an element by the given number of pixels on the X and Y axes. + * + * @param el The element to drag. + * @param page The E2E Page object. + * @param dragByX The number of pixels to drag on the X axis. Negative values drag left, positive values drag right. + * @param dragByY The number of pixels to drag on the Y axis. Negative values drag up, positive values drag down. + * @param startXCoord The X coordinate to start the drag from. Defaults to the center of the element. + * @param startYCoord The Y coordinate to start the drag from. Defaults to the center of the element. + * @param releaseDrag Whether to release the drag at the end of the gesture. Defaults to `true`. + * @param steps The number of steps to divide the drag into. More steps reduce velocity; fewer steps increase it. Use this to control whether velocity-based thresholds (e.g. full-swipe) are triggered, particularly in Safari where gesture velocity is calculated relative to animation frames. Defaults to `10`. + */ export const dragElementBy = async ( el: Locator | ElementHandle, page: E2EPage, @@ -46,10 +58,11 @@ export const dragElementBy = async ( /** * Drags an element by the given amount of pixels on the Y axis. + * * @param el The element to drag. * @param page The E2E Page object. - * @param dragByY The amount of pixels to drag the element by. - * @param startYCoord The Y coordinate to start the drag gesture at. Defaults to the center of the element. + * @param dragByY The number of pixels to drag on the Y axis. + * @param startYCoord The Y coordinate to start the drag from. Defaults to the center of the element. */ export const dragElementByYAxis = async ( el: Locator | ElementHandle, From ed95dac4a73c042c79848e32ed0afb8fe4936f79 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Apr 2026 09:12:03 -0700 Subject: [PATCH 08/11] docs(drag-element): grammar Co-authored-by: Shane --- core/src/utils/test/playwright/drag-element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index ea70e26aa7b..a4af90a1599 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -160,7 +160,7 @@ const moveElement = async (page: E2EPage, startX: number, startY: number, dragBy */ if (browser === 'webkit' && i % 2 === 0) { /** - * Repaintng every 2 steps is enough to keep the drag gesture smooth. + * Repainting every 2 steps is enough to keep the drag gesture smooth. * Repainting on every step makes the test slow, and repainting every * 4+ steps means Safari does not see enough frames to track the gesture * reliably. From f7df147211f2fcc5b534e9386cef4af3a0729cc1 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Apr 2026 09:19:48 -0700 Subject: [PATCH 09/11] test(item-sliding): create shared constants --- .../item-sliding/test/basic/item-sliding.e2e.ts | 7 +------ .../item-sliding/test/icons/item-sliding.e2e.ts | 6 +----- .../item-sliding/test/scroll-target/item-sliding.e2e.ts | 6 +----- .../item-sliding/test/shapes/item-sliding.e2e.ts | 6 +----- .../item-sliding/test/states/item-sliding.e2e.ts | 6 +----- core/src/components/item-sliding/test/test.utils.ts | 8 ++++++++ 6 files changed, 13 insertions(+), 26 deletions(-) diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index 4c99bbe82b2..1b15b0ed3ba 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -1,12 +1,7 @@ import { expect } from '@playwright/test'; import { configs, dragElementBy, test } from '@utils/test/playwright'; -/** - * Drag distances that reveal options without crossing the full swipe threshold - * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. - */ -const DRAG_DISTANCE_SINGLE_OPTION = 100; -const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +import { DRAG_DISTANCE_SINGLE_OPTION, DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; /** * item-sliding doesn't have mode-specific styling, diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index b249e35005e..75853c34665 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -/** - * Drag distances that reveal options without crossing the full swipe threshold - * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. - */ -const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; /** * item-sliding doesn't have mode-specific styling, diff --git a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts index 12af9489d7e..78a5667efbe 100644 --- a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -/** - * Drag distances that reveal options without crossing the full swipe threshold - * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. - */ -const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; /** * This behavior does not vary across modes/directions diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index f8396a8c82c..0c01e7add10 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -/** - * Drag distances that reveal options without crossing the full swipe threshold - * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. - */ -const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; /** * The shapes on the `item-option` do not vary by direction diff --git a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts index a273f2ab261..028b145ffb9 100644 --- a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -/** - * Drag distances that reveal options without crossing the full swipe threshold - * (optsWidth + SWIPE_MARGIN). A narrower options panel requires a shorter drag. - */ -const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; /** * This behavior does not vary across modes diff --git a/core/src/components/item-sliding/test/test.utils.ts b/core/src/components/item-sliding/test/test.utils.ts index 7bda6511efe..a4bda07a85d 100644 --- a/core/src/components/item-sliding/test/test.utils.ts +++ b/core/src/components/item-sliding/test/test.utils.ts @@ -1,6 +1,14 @@ import { expect } from '@playwright/test'; import type { E2EPage, ScreenshotFn } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe + * threshold (optsWidth + SWIPE_MARGIN). A narrower options panel + * requires a shorter drag. + */ +export const DRAG_DISTANCE_SINGLE_OPTION = 100; +export const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; + /** * Warning: This function will fail when in RTL mode. * TODO(FW-3711): Remove the `directions` config when this issue preventing From c763b6afbe7299b46a89738442ad4b03d54f695d Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Apr 2026 10:38:02 -0700 Subject: [PATCH 10/11] docs(item-sliding): add why steps were not increased --- .../test/basic/item-sliding.e2e.ts | 25 ++++++++++++++----- .../test/icons/item-sliding.e2e.ts | 4 +-- .../test/scroll-target/item-sliding.e2e.ts | 8 ++++++ .../test/shapes/item-sliding.e2e.ts | 4 +-- .../test/states/item-sliding.e2e.ts | 5 ++++ .../item-sliding/test/test.utils.ts | 9 ++++++- .../src/utils/test/playwright/drag-element.ts | 16 ++++++------ 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index 1b15b0ed3ba..479cd379532 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -1,7 +1,11 @@ import { expect } from '@playwright/test'; import { configs, dragElementBy, test } from '@utils/test/playwright'; -import { DRAG_DISTANCE_SINGLE_OPTION, DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; +import { + DRAG_DISTANCE_SINGLE_OPTION, + DRAG_DISTANCE_MULTIPLE_OPTIONS, + DRAG_STEPS_UNDER_FULL_SWIPE, +} from '../test.utils'; /** * item-sliding doesn't have mode-specific styling, @@ -27,7 +31,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf */ const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_SINGLE_OPTION : DRAG_DISTANCE_SINGLE_OPTION; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await expect(item).toHaveScreenshot(screenshot('item-sliding-start')); }); @@ -45,7 +49,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf */ const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await expect(item).toHaveScreenshot(screenshot('item-sliding-end')); }); @@ -62,7 +66,16 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co await page.goto(`/src/components/item-sliding/test/basic`, config); const item = page.locator('#item2'); - await dragElementBy(item, page, -DRAG_DISTANCE_MULTIPLE_OPTIONS, 0, undefined, undefined, true, 15); + await dragElementBy( + item, + page, + -DRAG_DISTANCE_MULTIPLE_OPTIONS, + 0, + undefined, + undefined, + true, + DRAG_STEPS_UNDER_FULL_SWIPE + ); await page.waitForChanges(); // item-sliding doesn't have an easy way to tell whether it's fully open so just screenshot it @@ -142,7 +155,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const item = page.locator('ion-item-sliding'); const dragByX = direction == 'rtl' ? -DRAG_DISTANCE_MULTIPLE_OPTIONS : DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-left`)); @@ -183,7 +196,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const item = page.locator('ion-item-sliding'); const dragByX = direction == 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-right`)); diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index 75853c34665..4716a3054c5 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS, DRAG_STEPS_UNDER_FULL_SWIPE } from '../test.utils'; /** * item-sliding doesn't have mode-specific styling, @@ -28,7 +28,7 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf */ const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); // Convert camelCase ids to kebab-case for screenshot file names diff --git a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts index 78a5667efbe..9fc0b5e9446 100644 --- a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts @@ -18,6 +18,14 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); + /** + * No need to increase steps to prevent the full swipe threshold + * from being crossed because: + * - we are not testing swipe behavior here + * - increasing steps is only in Webkit since it accumulates velocity + * faster than other browsers, and this test is skipped in Webkit, so + * the default step count is safe to use + */ await dragElementBy(itemSlidingEl, page, -DRAG_DISTANCE_MULTIPLE_OPTIONS, 0, undefined, undefined, false); /** diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index 0c01e7add10..624196217a0 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; -import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS, DRAG_STEPS_UNDER_FULL_SWIPE } from '../test.utils'; /** * The shapes on the `item-option` do not vary by direction @@ -23,7 +23,7 @@ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screensh */ const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, 15); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-${shape}`)); diff --git a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts index 028b145ffb9..f30660803db 100644 --- a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts @@ -20,6 +20,11 @@ configs({ modes: ['ionic-md', 'md', 'ios'], directions: ['ltr'] }).forEach(({ ti */ const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; + /** + * No need to increase steps to prevent the full swipe threshold from + * being crossed because the option is disabled, so the option will + * never expand fully regardless of drag speed. + */ await dragElementBy(item, page, dragByX); await page.waitForChanges(); diff --git a/core/src/components/item-sliding/test/test.utils.ts b/core/src/components/item-sliding/test/test.utils.ts index a4bda07a85d..b18803b4af0 100644 --- a/core/src/components/item-sliding/test/test.utils.ts +++ b/core/src/components/item-sliding/test/test.utils.ts @@ -3,12 +3,19 @@ import type { E2EPage, ScreenshotFn } from '@utils/test/playwright'; /** * Drag distances that reveal options without crossing the full swipe - * threshold (optsWidth + SWIPE_MARGIN). A narrower options panel + * threshold (`optsWidth` + `SWIPE_MARGIN`). A narrower options panel * requires a shorter drag. */ export const DRAG_DISTANCE_SINGLE_OPTION = 100; export const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; +/** + * The number of drag steps used when revealing options. A higher step + * count slows the drag velocity, keeping it below the full swipe + * threshold in WebKit. See `dragElementBy` for more details. + */ +export const DRAG_STEPS_UNDER_FULL_SWIPE = 15; + /** * Warning: This function will fail when in RTL mode. * TODO(FW-3711): Remove the `directions` config when this issue preventing diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index a4af90a1599..81f6f9a6c9e 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -153,17 +153,19 @@ const moveElement = async (page: E2EPage, startX: number, startY: number, dragBy await page.mouse.move(middleX, middleY); /** - * In Safari, gesture velocity is calculated relative to animation frames. - * Without waiting for a repaint, consecutive `mouse.move` events arrive - * with ~0ms time delta and velocity never accumulates, causing gesture + * In Safari, gesture velocity is calculated relative to animation + * frames, causing velocity to accumulate faster than in other + * browsers. Without waiting for a repaint, consecutive `mouse.move` + * events arrive with ~0ms time delta and velocity never accumulates, + * causing gesture * detection to fail. */ if (browser === 'webkit' && i % 2 === 0) { /** - * Repainting every 2 steps is enough to keep the drag gesture smooth. - * Repainting on every step makes the test slow, and repainting every - * 4+ steps means Safari does not see enough frames to track the gesture - * reliably. + * Repainting every 2 steps is enough to keep the drag gesture + * smooth. Repainting on every step makes the test slow, and + * repainting every 4+ steps means Safari does not see enough + * frames to track the gesture reliably. */ await page.evaluate(() => new Promise(requestAnimationFrame)); } From 27a8b6ad6f3b6e1291a1e1bd932b8f9a34c05323 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Apr 2026 10:40:20 -0700 Subject: [PATCH 11/11] test(item-sliding): add waitForChanges --- core/src/components/item-sliding/test/basic/item-sliding.e2e.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index 479cd379532..de0ad68de65 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -32,6 +32,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_SINGLE_OPTION : DRAG_DISTANCE_SINGLE_OPTION; await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); + await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot('item-sliding-start')); }); @@ -50,6 +51,7 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); + await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot('item-sliding-end')); });