Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Defaults to `'visible'`. Can be either:
* `'attached'` - wait for element to be present in DOM.
* `'detached'` - wait for element to not be present in DOM.
* `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element without
any content or with `display:none` has an empty bounding box and is not considered visible.
any content or with `display:none` has an empty bounding box and is not considered visible. Metadata elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered visible if they're attached.
* `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or `visibility:hidden`.
This is opposite to the `'visible'` option.

Expand Down
17 changes: 17 additions & 0 deletions packages/injected/src/domUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ export function computeBox(element: Element): Box {
return { rect, style, visible: rect.width > 0 && rect.height > 0, inline: style.display === 'inline' };
}

/**
* Elements that don't directly contribute to the visible content of the document.
* Generally these are elements for which you don't care about their visibility implicitly.
*/
export function isMetadataElement(element: Element): boolean {
const localName = element.localName;
return (
localName === 'base'
|| localName === 'link'
|| localName === 'meta'
|| localName === 'script'
|| localName === 'source'
|| localName === 'style'
|| localName === 'title'
);
}

export function isElementVisible(element: Element): boolean {
return computeBox(element).visible;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { parseAttributeSelector, parseSelector, stringifySelector, visitAllSelec
import { cacheNormalizedWhitespaces, normalizeWhiteSpace, trimStringWithEllipsis } from '@isomorphic/stringUtils';

import { generateAriaTree, getAllElementsMatchingExpectAriaTemplate, matchesExpectAriaTemplate, renderAriaTree } from './ariaSnapshot';
import { enclosingShadowRootOrDocument, isElementVisible, isInsideScope, parentElementOrShadowHost, setGlobalOptions } from './domUtils';
import { enclosingShadowRootOrDocument, isElementVisible, isMetadataElement, isInsideScope, parentElementOrShadowHost, setGlobalOptions } from './domUtils';
import { Highlight } from './highlight';
import { kLayoutSelectorNames, layoutSelectorScore } from './layoutSelectorUtils';
import { createReactEngine } from './reactSelectorEngine';
Expand Down Expand Up @@ -103,6 +103,7 @@ export class InjectedScript {
getElementAccessibleDescription,
getElementAccessibleName,
isElementVisible,
isMetadataElement,
isInsideScope,
normalizeWhiteSpace,
parseAriaSnapshot,
Expand Down
12 changes: 9 additions & 3 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14590,7 +14590,9 @@ export interface Locator {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down Expand Up @@ -22027,7 +22029,9 @@ interface ElementHandleWaitForSelectorOptions {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down Expand Up @@ -22542,7 +22546,9 @@ interface PageWaitForSelectorOptions {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down
9 changes: 8 additions & 1 deletion packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,14 @@ export class Frame extends SdkObject {
throw injected.createStacklessError('Element is not attached to the DOM');
const elements = injected.querySelectorAll(info.parsed, root || document);
const element: Element | undefined = elements[0];
const visible = element ? injected.utils.isElementVisible(element) : false;
const visible = element
?
injected.utils.isMetadataElement(element)
// For e.g. `<title>`, there won't be a bounding box but it's safe to
// assume `waitForSelector('title')` is supposed to resolve if the element is attached.
? true
: injected.utils.isElementVisible(element)
: false;
let log = '';
if (elements.length > 1) {
if (info.strict)
Expand Down
12 changes: 9 additions & 3 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14590,7 +14590,9 @@ export interface Locator {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down Expand Up @@ -22027,7 +22029,9 @@ interface ElementHandleWaitForSelectorOptions {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down Expand Up @@ -22542,7 +22546,9 @@ interface PageWaitForSelectorOptions {
* - `'attached'` - wait for element to be present in DOM.
* - `'detached'` - wait for element to not be present in DOM.
* - `'visible'` - wait for element to have non-empty bounding box and no `visibility:hidden`. Note that element
* without any content or with `display:none` has an empty bounding box and is not considered visible.
* without any content or with `display:none` has an empty bounding box and is not considered visible. Metadata
* elements (`<base>`, `<link>`, `<meta>`, `<script>`, `<source>`, `<style>`, and `<title>`) are always considered
* visible if they're attached.
* - `'hidden'` - wait for element to be either detached from DOM, or have an empty bounding box or
* `visibility:hidden`. This is opposite to the `'visible'` option.
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/page/page-wait-for-selector-2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ it('should not consider visible when zero-sized', async ({ page, server }) => {
expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy();
});

it('should consider visible if metadata', async ({ page }) => {
await page.setContent(
`<base>`
+ `<meta name="viewport" content="width=device-width, initial-scale=1">`
+ `<link rel="icon" type="image/x-icon" href="/static/favicon.ico">`
+ `<style></style>`
+ `<script></script>`
+ `<template>Not visible</template>`
+ `<title>test</title>`
+ `<video controls><source src="foo.webm"></video>`
);
await page.waitForSelector('base');
await page.waitForSelector('link[rel="icon"]');
await page.waitForSelector('meta[name="viewport"]');
await page.waitForSelector('script');
await page.waitForSelector('source');
await page.waitForSelector('style');
await expect(page.waitForSelector('template', { timeout: 1000 })).rejects.toThrow('page.waitForSelector: Timeout 1000ms exceeded');
await page.waitForSelector('title');
});

it('should wait for visible recursively', async ({ page, server }) => {
let divVisible = false;
const waitForSelector = page.waitForSelector('div#inner').then(() => divVisible = true);
Expand Down
Loading