Skip to content

Commit ec97bf1

Browse files
mschileclaude
andauthored
fix: scroll elements into view on both axes during actionability (#34108)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4dfb9bb commit ec97bf1

8 files changed

Lines changed: 82 additions & 24 deletions

File tree

cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Fixed an issue where the [`userAgent`](https://on.cypress.io/configuration#Browser) configuration option was not applied when running tests in the experimental WebKit browser. Fixes [#33349](https://github.com/cypress-io/cypress/issues/33349).
1515
- Fixed an issue where, in the experimental WebKit browser, a request to focus the browser window was silently ignored, so the window could remain in the background. The active page is now correctly brought to the front. Addressed in [#34137](https://github.com/cypress-io/cypress/pull/34137).
1616
- Fixed an issue where opening an unconfigured project from a git repository sub-directory (such as a monorepo package) skipped project setup. Fixes [#27410](https://github.com/cypress-io/cypress/issues/27410) and [#29544](https://github.com/cypress-io/cypress/issues/29544).
17+
- Fixed an issue where interacting with an element inside a horizontally-scrollable container could scroll the element to the container's right edge, placing it underneath a right-floating `position: sticky` or `position: fixed` element and causing the action to fail or land on the wrong element. Elements are now scrolled to their top, leftmost point as documented. Fixes [#33884](https://github.com/cypress-io/cypress/issues/33884). Fixed in [#34108](https://github.com/cypress-io/cypress/pull/34108).
1718

1819
## 15.18.0
1920

packages/driver/cypress/e2e/commands/actions/check.cy.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ describe('src/cy/commands/actions/check', () => {
221221
cy.get(':checkbox:first').check({ scrollBehavior: 'bottom' })
222222

223223
cy.get(':checkbox:first').then((el) => {
224-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
224+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
225225
})
226226
})
227227

@@ -246,7 +246,7 @@ describe('src/cy/commands/actions/check', () => {
246246
cy.get(':checkbox:first').check()
247247

248248
cy.get(':checkbox:first').then((el) => {
249-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
249+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
250250
})
251251
})
252252

@@ -258,7 +258,7 @@ describe('src/cy/commands/actions/check', () => {
258258
cy.get(':checkbox:first').check()
259259

260260
cy.get(':checkbox:first').then((el) => {
261-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center' })
261+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center', inline: 'center' })
262262
})
263263
})
264264

@@ -270,7 +270,7 @@ describe('src/cy/commands/actions/check', () => {
270270
cy.get(':checkbox:first').check()
271271

272272
cy.get(':checkbox:first').then((el) => {
273-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest' })
273+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest', inline: 'nearest' })
274274
})
275275
})
276276

@@ -296,7 +296,7 @@ describe('src/cy/commands/actions/check', () => {
296296
cy.get(':checkbox:first').check()
297297

298298
cy.get(':checkbox:first').then((el) => {
299-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start' })
299+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start', inline: 'start' })
300300
})
301301
})
302302

packages/driver/cypress/e2e/commands/actions/clear.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
107107
cy.get('input:first').clear({ scrollBehavior: 'bottom' })
108108

109109
cy.get('input:first').then((el) => {
110-
expect(el[0].scrollIntoView).calledWith({ block: 'end' })
110+
expect(el[0].scrollIntoView).calledWith({ block: 'end', inline: 'end' })
111111
})
112112
})
113113

@@ -143,7 +143,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
143143
cy.get('input:first').clear()
144144

145145
cy.get('input:first').then((el) => {
146-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start' })
146+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start', inline: 'start' })
147147
})
148148
})
149149

packages/driver/cypress/e2e/commands/actions/click.cy.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,7 @@ describe('src/cy/commands/actions/click', () => {
12591259
cy.get('input:first').click({ scrollBehavior: 'bottom' })
12601260

12611261
cy.get('input:first').then((el) => {
1262-
expect(el[0].scrollIntoView).calledWith({ block: 'end' })
1262+
expect(el[0].scrollIntoView).calledWith({ block: 'end', inline: 'end' })
12631263
})
12641264
})
12651265

@@ -1283,7 +1283,7 @@ describe('src/cy/commands/actions/click', () => {
12831283
cy.get('input:first').click()
12841284

12851285
cy.get('input:first').then((el) => {
1286-
expect(el[0].scrollIntoView).calledWith({ block: 'end' })
1286+
expect(el[0].scrollIntoView).calledWith({ block: 'end', inline: 'end' })
12871287
})
12881288
})
12891289

@@ -1295,7 +1295,7 @@ describe('src/cy/commands/actions/click', () => {
12951295
cy.get('input:first').click()
12961296

12971297
cy.get('input:first').then((el) => {
1298-
expect(el[0].scrollIntoView).calledWith({ block: 'center' })
1298+
expect(el[0].scrollIntoView).calledWith({ block: 'center', inline: 'center' })
12991299
})
13001300
})
13011301

@@ -1307,7 +1307,7 @@ describe('src/cy/commands/actions/click', () => {
13071307
cy.get('input:first').click()
13081308

13091309
cy.get('input:first').then((el) => {
1310-
expect(el[0].scrollIntoView).calledWith({ block: 'nearest' })
1310+
expect(el[0].scrollIntoView).calledWith({ block: 'nearest', inline: 'nearest' })
13111311
})
13121312
})
13131313

@@ -1331,7 +1331,63 @@ describe('src/cy/commands/actions/click', () => {
13311331
cy.get('input:first').click()
13321332

13331333
cy.get('input:first').then((el) => {
1334-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start' })
1334+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start', inline: 'start' })
1335+
})
1336+
})
1337+
1338+
it('horizontally scrolls element to the leftmost point, away from a right-floating sticky element', () => {
1339+
cy.viewport(800, 400)
1340+
1341+
const $body = cy.$$('body')
1342+
1343+
$body.children().remove()
1344+
1345+
const $container = $('<div></div>')
1346+
.css({
1347+
width: '512px',
1348+
height: '128px',
1349+
overflowX: 'scroll',
1350+
})
1351+
.appendTo($body)
1352+
1353+
const $row = $('<div></div>')
1354+
.css({
1355+
width: '2048px',
1356+
height: '128px',
1357+
display: 'flex',
1358+
})
1359+
.appendTo($container)
1360+
1361+
// spacer pushing the target to the right of the container's viewport
1362+
$('<div></div>').css({ width: '512px', height: '128px' }).appendTo($row)
1363+
1364+
const $target = $('<div></div>')
1365+
.attr('id', 'target')
1366+
.css({ width: '128px', height: '128px', background: 'green' })
1367+
.appendTo($row)
1368+
1369+
$('<div></div>').css({ width: '512px', height: '128px' }).appendTo($row)
1370+
1371+
// a sticky element floating on the right edge of the scrollable container
1372+
$('<div></div>')
1373+
.attr('id', 'right-sticky')
1374+
.css({
1375+
position: 'sticky',
1376+
right: '0',
1377+
width: '256px',
1378+
height: '128px',
1379+
background: 'yellow',
1380+
})
1381+
.appendTo($row)
1382+
1383+
const clicked = cy.stub()
1384+
1385+
$target.on('click', clicked)
1386+
1387+
// with `inline: 'nearest'` the target would be scrolled under the
1388+
// right-sticky element and the click would never reach it
1389+
cy.get('#target').click().then(() => {
1390+
expect(clicked).to.be.calledOnce
13351391
})
13361392
})
13371393

packages/driver/cypress/e2e/commands/actions/selectFile.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ is being covered by another element:
763763
cy.get('#scroll').selectFile({ contents: '@foo' }, { scrollBehavior: 'bottom' })
764764

765765
cy.get('#scroll').then((el) => {
766-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
766+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
767767
})
768768
})
769769
})

packages/driver/cypress/e2e/commands/actions/trigger.cy.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ describe('src/cy/commands/actions/trigger', () => {
608608
cy.get('button:first').trigger('mouseover', { scrollBehavior: 'bottom' })
609609

610610
cy.get('button:first').then((el) => {
611-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
611+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
612612
})
613613
})
614614

@@ -633,7 +633,7 @@ describe('src/cy/commands/actions/trigger', () => {
633633
cy.get('button:first').trigger('mouseover')
634634

635635
cy.get('button:first').then((el) => {
636-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
636+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
637637
})
638638
})
639639

@@ -645,7 +645,7 @@ describe('src/cy/commands/actions/trigger', () => {
645645
cy.get('button:first').trigger('mouseover')
646646

647647
cy.get('button:first').then((el) => {
648-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center' })
648+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center', inline: 'center' })
649649
})
650650
})
651651

@@ -657,7 +657,7 @@ describe('src/cy/commands/actions/trigger', () => {
657657
cy.get('button:first').trigger('mouseover')
658658

659659
cy.get('button:first').then((el) => {
660-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest' })
660+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest', inline: 'nearest' })
661661
})
662662
})
663663

@@ -683,7 +683,7 @@ describe('src/cy/commands/actions/trigger', () => {
683683
cy.get('button:first').trigger('mouseover')
684684

685685
cy.get('button:first').then((el) => {
686-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start' })
686+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start', inline: 'start' })
687687
})
688688
})
689689

packages/driver/cypress/e2e/commands/actions/type.cy.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ describe('src/cy/commands/actions/type - #type', () => {
378378
cy.get(':text:first').type('foo', { scrollBehavior: 'bottom' })
379379

380380
cy.get(':text:first').then((el) => {
381-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
381+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
382382
})
383383
})
384384

@@ -402,7 +402,7 @@ describe('src/cy/commands/actions/type - #type', () => {
402402
cy.get(':text:first').type('foo')
403403

404404
cy.get(':text:first').then((el) => {
405-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end' })
405+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'end', inline: 'end' })
406406
})
407407
})
408408

@@ -414,7 +414,7 @@ describe('src/cy/commands/actions/type - #type', () => {
414414
cy.get(':text:first').type('foo')
415415

416416
cy.get(':text:first').then((el) => {
417-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center' })
417+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'center', inline: 'center' })
418418
})
419419
})
420420

@@ -426,7 +426,7 @@ describe('src/cy/commands/actions/type - #type', () => {
426426
cy.get(':text:first').type('foo')
427427

428428
cy.get(':text:first').then((el) => {
429-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest' })
429+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'nearest', inline: 'nearest' })
430430
})
431431
})
432432

@@ -450,7 +450,7 @@ describe('src/cy/commands/actions/type - #type', () => {
450450
cy.get(':text:first').type('foo')
451451

452452
cy.get(':text:first').then((el) => {
453-
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start' })
453+
expect(el[0].scrollIntoView).to.be.calledWith({ block: 'start', inline: 'start' })
454454
})
455455
})
456456

packages/driver/src/cy/actionability.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ const verify = function (cy, $el, config, options, callbacks: VerifyCallbacks) {
535535
const removeScrollBehaviorFix = addScrollBehaviorFix($el)
536536

537537
debug('scrollIntoView:', $el[0])
538-
$el.get(0).scrollIntoView({ block: scrollBehavior })
538+
// Mirror the scroll behavior onto both axes.
539+
$el.get(0).scrollIntoView({ block: scrollBehavior, inline: scrollBehavior })
539540

540541
removeScrollBehaviorFix()
541542

0 commit comments

Comments
 (0)