diff --git a/docs/src/electron-api/class-electron.md b/docs/src/electron-api/class-electron.md index 60e64080e0551..e95c1edb1b6a1 100644 --- a/docs/src/electron-api/class-electron.md +++ b/docs/src/electron-api/class-electron.md @@ -41,9 +41,7 @@ const { _electron: electron } = require('playwright'); ``` **Supported Electron versions are:** -* v12.2.0+ -* v13.4.0+ -* v14+ +* v21+ **Known issues:** diff --git a/docs/src/mobile-api/class-android.md b/docs/src/mobile-api/class-android.md index 4427622b6af51..68305f4b7f748 100644 --- a/docs/src/mobile-api/class-android.md +++ b/docs/src/mobile-api/class-android.md @@ -7,7 +7,7 @@ Playwright has **experimental** support for Android automation. This includes Ch *Requirements* * Android device or AVD Emulator. * [ADB daemon](https://developer.android.com/studio/command-line/adb) running and authenticated with your device. Typically running `adb devices` is all you need to do. -* [`Chrome 87`](https://play.google.com/store/apps/details?id=com.android.chrome) or newer installed on the device +* [`Chrome 105`](https://play.google.com/store/apps/details?id=com.android.chrome) or newer installed on the device * "Enable command line on non-rooted devices" enabled in `chrome://flags`. *Known limitations* diff --git a/packages/injected/src/domUtils.ts b/packages/injected/src/domUtils.ts index c88184506a424..557e4ca9f35af 100644 --- a/packages/injected/src/domUtils.ts +++ b/packages/injected/src/domUtils.ts @@ -21,9 +21,6 @@ let globalOptions: GlobalOptions = {}; export function setGlobalOptions(options: GlobalOptions) { globalOptions = options; } -export function getGlobalOptions(): GlobalOptions { - return globalOptions; -} export function isInsideScope(scope: Node, element: Element | undefined): boolean { while (element) { @@ -100,23 +97,31 @@ function computeElementStyleVisibilityVisible(element: Element, style?: CSSStyle // Element.checkVisibility checks for content-visibility and also looks at // styles up the flat tree including user-agent ShadowRoots, such as the // details element for example. - // All the browser implement it, but WebKit has a bug which prevents us from using it: - // https://bugs.webkit.org/show_bug.cgi?id=264733 - // @ts-ignore - if (Element.prototype.checkVisibility && globalOptions.browserNameForWorkarounds !== 'webkit') { - if (!element.checkVisibility()) - return false; - } else { - // Manual workaround for WebKit that does not have checkVisibility. - const detailsOrSummary = element.closest('details,summary'); - if (detailsOrSummary !== element && detailsOrSummary?.nodeName === 'DETAILS' && !(detailsOrSummary as HTMLDetailsElement).open) - return false; - } + if (!element.checkVisibility() && !isWebKitListBoxOptionVisible(element, style)) + return false; if (style.visibility !== 'visible') return false; return true; } +export function isListBoxSelect(select: HTMLSelectElement): boolean { + return select.multiple || select.size > 1; +} + +// WebKit does not create renderers for list box options, so checkVisibility() returns false for them. +function isWebKitListBoxOptionVisible(element: Element, style: CSSStyleDeclaration): boolean { + if (globalOptions.browserNameForWorkarounds !== 'webkit' || element.nodeName !== 'OPTION' || style.display === 'none') + return false; + const select = element.closest('select'); + if (!select || !isListBoxSelect(select)) + return false; + for (let e = element.parentElement; e && e !== select; e = e.parentElement) { + if (getElementComputedStyle(e)?.display === 'none') + return false; + } + return isElementStyleVisibilityVisible(select); +} + export function computeBox(element: Element) { // Note: this logic should be similar to waitForDisplayedAtStablePosition() to avoid surprises. const style = getElementComputedStyle(element); diff --git a/packages/injected/src/roleUtils.ts b/packages/injected/src/roleUtils.ts index 6216b45b04c77..3b274129d345a 100644 --- a/packages/injected/src/roleUtils.ts +++ b/packages/injected/src/roleUtils.ts @@ -16,7 +16,7 @@ import * as css from '@isomorphic/cssTokenizer'; -import { beginDOMCaches, closestCrossShadow, elementSafeTagName, enclosingShadowRootOrDocument, endDOMCaches, getElementComputedStyle, isElementStyleVisibilityVisible, isVisibleTextNode, parentElementOrShadowHost } from './domUtils'; +import { beginDOMCaches, closestCrossShadow, elementSafeTagName, enclosingShadowRootOrDocument, endDOMCaches, getElementComputedStyle, isElementStyleVisibilityVisible, isListBoxSelect, isVisibleTextNode, parentElementOrShadowHost } from './domUtils'; import type { AriaRole } from '@isomorphic/ariaSnapshot'; @@ -155,7 +155,7 @@ const kImplicitRoleByTagName: { [tagName: string]: (e: Element) => AriaRole | nu 'PROGRESS': () => 'progressbar', 'SEARCH': () => 'search', 'SECTION': (e: Element) => hasExplicitAccessibleName(e) ? 'region' : null, - 'SELECT': (e: Element) => e.hasAttribute('multiple') || (e as HTMLSelectElement).size > 1 ? 'listbox' : 'combobox', + 'SELECT': (e: Element) => isListBoxSelect(e as HTMLSelectElement) ? 'listbox' : 'combobox', 'STRONG': () => 'strong', 'SUB': () => 'subscript', 'SUP': () => 'superscript', @@ -341,7 +341,7 @@ function belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element: Element): boolean // When parent has a shadow root, all light dom children must be assigned to a slot, // otherwise they are not rendered and considered hidden for aria. - // Note: we can remove this logic once WebKit supports `Element.checkVisibility`. + // Note: needed for