From ea90f82519408eaa8e873d8b1294eea14df229f6 Mon Sep 17 00:00:00 2001 From: Matthew Schile Date: Wed, 22 Jan 2025 10:10:21 -0700 Subject: [PATCH 1/8] fix: visibility issue when parent element is clipping --- .circleci/workflows.yml | 15 ++++------ .../driver/cypress/e2e/dom/visibility.cy.ts | 30 ++++++++++++++----- packages/driver/src/dom/visibility.ts | 8 +++-- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index 79c4f8933ca2..28266f43c7c1 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,9 +30,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'chore/update_vue_test_utils' - - 'publish-binary' - - 'cacie/29590/document-domain-subdomains' + - 'mschile/issue-30922' # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -43,8 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'chore/update_vue_test_utils', << pipeline.git.branch >> ] - - equal: [ 'cacie/fix-hook-test-stack-analysis', << pipeline.git.branch >> ] + - equal: [ 'mschile/issue-30922', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -55,8 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'chore/update_binary_branch', << pipeline.git.branch >> ] - - equal: [ 'cacie/fix-hook-test-stack-analysis', << pipeline.git.branch >> ] + - equal: [ 'mschile/issue-30922', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -79,8 +75,7 @@ windowsWorkflowFilters: &windows-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'ryanm/chore/electron-33-upgrade', << pipeline.git.branch >> ] - - equal: [ 'cacie/fix-hook-test-stack-analysis', << pipeline.git.branch >> ] + - equal: [ 'mschile/issue-30922', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -156,7 +151,7 @@ commands: name: Set environment variable to determine whether or not to persist artifacts command: | echo "Setting SHOULD_PERSIST_ARTIFACTS variable" - echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "chore/update_vue_test_utils" && "$CIRCLE_BRANCH" != cacie/fix-hook-test-stack-analysis ]]; then + echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "mschile/issue-30922" ]]; then export SHOULD_PERSIST_ARTIFACTS=true fi' >> "$BASH_ENV" # You must run `setup_should_persist_artifacts` command and be using bash before running this command diff --git a/packages/driver/cypress/e2e/dom/visibility.cy.ts b/packages/driver/cypress/e2e/dom/visibility.cy.ts index 50f7a86f7708..47fe3c4b636c 100644 --- a/packages/driver/cypress/e2e/dom/visibility.cy.ts +++ b/packages/driver/cypress/e2e/dom/visibility.cy.ts @@ -995,10 +995,6 @@ describe('src/cypress/dom/visibility', () => { }) it('is visible when element is statically positioned and parent element is absolutely positioned and ancestor has overflow hidden', function () { - const add = (el) => { - return $(el).appendTo(cy.$$('body')) - } - cy.$$('body').empty() const el = add(` @@ -1015,10 +1011,6 @@ describe('src/cypress/dom/visibility', () => { }) it('is visible when element is relatively positioned and parent element is absolutely positioned and ancestor has overflow auto', function () { - const add = (el) => { - return $(el).appendTo(cy.$$('body')) - } - cy.$$('body').empty() const el = add(` @@ -1043,6 +1035,28 @@ describe('src/cypress/dom/visibility', () => { expect(el.find('#visible-button')).to.be.visible }) + + it('is hidden when parent element is absolutely position and offset parent is a decendent of the ancestor', function () { + cy.$$('body').empty() + + add(` +
+
+
+
+ test test-1 +
+
+ test test-2 +
+
+
+
+ `) + + cy.contains('test-2').should('not.be.visible') + cy.contains('test-1').should('be.visible') + }) }) describe('css clip-path', () => { diff --git a/packages/driver/src/dom/visibility.ts b/packages/driver/src/dom/visibility.ts index f9373dc6d75a..5028d9a73f82 100644 --- a/packages/driver/src/dom/visibility.ts +++ b/packages/driver/src/dom/visibility.ts @@ -258,10 +258,14 @@ const canClipContent = function ($el: JQuery, $ancestor: JQuery Date: Thu, 23 Jan 2025 11:29:02 -0700 Subject: [PATCH 2/8] fix visibility issue with select elements --- .../driver/cypress/e2e/dom/visibility.cy.ts | 47 ++++++++++++++++++- .../driver/src/dom/elements/elementHelpers.ts | 2 +- packages/driver/src/dom/elements/find.ts | 4 +- packages/driver/src/dom/visibility.ts | 45 +++++++++++++----- 4 files changed, 81 insertions(+), 17 deletions(-) diff --git a/packages/driver/cypress/e2e/dom/visibility.cy.ts b/packages/driver/cypress/e2e/dom/visibility.cy.ts index 47fe3c4b636c..fa9136c813cc 100644 --- a/packages/driver/cypress/e2e/dom/visibility.cy.ts +++ b/packages/driver/cypress/e2e/dom/visibility.cy.ts @@ -6,7 +6,7 @@ describe('src/cypress/dom/visibility', () => { return $(el).appendTo(cy.$$('body')) } - const reasonIs = ($el, str) => { + const reasonIs = ($el: JQuery, str: string) => { expect(dom.getReasonIsHidden($el)).to.eq(str) } @@ -1057,6 +1057,51 @@ describe('src/cypress/dom/visibility', () => { cy.contains('test-2').should('not.be.visible') cy.contains('test-1').should('be.visible') }) + + it('is hidden when element is an option and the parent has overflow clip', function () { + cy.$$('body').empty() + + add(` +
+
+ +
+ `) + + cy.get('option').should('not.be.visible').then(($el) => { + reasonIs($el, 'This element `` is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: `hidden`, `clip`, `scroll` or `auto`') + }) + + cy.get('select').should('not.be.visible').then(($el) => { + reasonIs($el, 'This element ` + + + + + + `) + + cy.get('option').should('be.visible') + cy.get('optgroup').should('be.visible') + cy.get('select').should('be.visible') + }) }) describe('css clip-path', () => { diff --git a/packages/driver/src/dom/elements/elementHelpers.ts b/packages/driver/src/dom/elements/elementHelpers.ts index f8a74954ce0d..d296cfcd3cea 100644 --- a/packages/driver/src/dom/elements/elementHelpers.ts +++ b/packages/driver/src/dom/elements/elementHelpers.ts @@ -6,7 +6,7 @@ const inputTypeNeedSingleValueChangeRe = /^(date|time|week|month|datetime-local) const valueIsNumberTypeRe = /progress|meter|li/ -export const getTagName = (el) => { +export const getTagName = (el: HTMLElement) => { const tagName = el.tagName || '' return tagName.toLowerCase() diff --git a/packages/driver/src/dom/elements/find.ts b/packages/driver/src/dom/elements/find.ts index caceed6d7295..8e82adf10dfe 100644 --- a/packages/driver/src/dom/elements/find.ts +++ b/packages/driver/src/dom/elements/find.ts @@ -45,7 +45,7 @@ export const getAllParents = (el: HTMLElement, untilSelectorOrEl?: string | HTML return collectParents([], el) } -export const findParent = (el, condition) => { +export const findParent = (el: HTMLElement, condition: (parent: HTMLElement, node: HTMLElement) => R) => { const collectParent = (node) => { const parent = getParentNode(node) @@ -61,7 +61,7 @@ export const findParent = (el, condition) => { return collectParent(el) } -export const getFirstParentWithTagName = ($el, tagName) => { +export const getFirstParentWithTagName = ($el: JQuery, tagName: string) => { if (isUndefinedOrHTMLBodyDoc($el) || !tagName) { return null } diff --git a/packages/driver/src/dom/visibility.ts b/packages/driver/src/dom/visibility.ts index 5028d9a73f82..14213d1f476f 100644 --- a/packages/driver/src/dom/visibility.ts +++ b/packages/driver/src/dom/visibility.ts @@ -5,7 +5,7 @@ import $elements from './elements' import $coordinates from './coordinates' import * as $transform from './transform' -const { isElement, isSelect, isBody, isHTML, isOption, isOptgroup, getParent, getFirstParentWithTagName, isAncestor, isChild, getAllParents, isDescendent, isUndefinedOrHTMLBodyDoc, elOrAncestorIsFixedOrSticky, isDetached, isFocusable, stringify: stringifyElement } = $elements +const { isElement, isBody, isHTML, isOption, isOptgroup, getParent, getFirstParentWithTagName, isAncestor, isChild, getAllParents, isDescendent, isUndefinedOrHTMLBodyDoc, elOrAncestorIsFixedOrSticky, isDetached, isFocusable, stringify: stringifyElement } = $elements const fixedOrAbsoluteRe = /(fixed|absolute)/ @@ -36,6 +36,18 @@ const ensureEl = (el, methodName) => { } } +const getFirstSelectParentFromEl = ($el: JQuery) => { + const $select = getFirstParentWithTagName($el, 'select') + + // check $select.length here first + // they may have not put the option into a select el + if ($select?.length) { + return $select + } + + return null +} + const isStrictlyHidden = (el: HTMLElement, methodName = 'isStrictlyHidden()', options = { checkOpacity: true }, recurse?) => { ensureEl(el, methodName) const $el = $jquery.wrap(el) @@ -53,13 +65,9 @@ const isStrictlyHidden = (el: HTMLElement, methodName = 'isStrictlyHidden()', op } // if its parent select is visible, then it's not hidden - const $select = getFirstParentWithTagName($el, 'select') + const $select = getFirstSelectParentFromEl($el) - // check $select.length here first - // they may have not put the option into a select el, - // in which case it will fall through to regular visibility logic - if ($select && $select.length) { - // if the select is hidden, the options in it are hidden too + if ($select) { return recurse ? recurse($select[0], methodName, options) : isStrictlyHidden($select[0], methodName, options) } } @@ -102,6 +110,15 @@ const isHiddenByAncestors = (el, methodName = 'isHiddenByAncestors()', options = ensureEl(el, methodName) const $el = $jquery.wrap(el) + // an option is considered hidden by ancestors if its parent select is hidden + if (isOption(el) || isOptgroup(el)) { + const $select = getFirstSelectParentFromEl($el) + + if ($select) { + return isHiddenByAncestors($select[0], methodName, options) + } + } + // we do some calculations taking into account the parents // to see if its hidden by a parent if (elIsHiddenByAncestors($el, options.checkOpacity)) { @@ -234,11 +251,6 @@ const canClipContent = function ($el: JQuery, $ancestor: JQuery