feat(selectors): disable proximity selectors (#4659)

These are not ready for prime time yet.
This commit is contained in:
Dmitry Gozman 2020-12-10 10:04:10 -08:00 committed by GitHub
parent 84ff20f193
commit c8e9b0542b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 10 additions and 72 deletions

View File

@ -88,7 +88,9 @@ Playwright also supports the following CSS extensions:
* `:text("string")` - Matches elements that contain specific text node. Learn more about [text selector](./selectors.md#css-extension-text).
* `:visible` - Matches only visible elements. Learn more about [visible selector](./selectors.md#css-extension-visible).
* `:light(selector)` - Matches in the light DOM only as opposite to piercing open shadow roots. Learn more about [shadow piercing](./selectors.md#shadow-piercing).
<!--
* `:right-of(selector)`, `:left-of(selector)`, `:above(selector)`, `:below(selector)`, `:near(selector)`, `:within(selector)` - Match elements based on their relative position to another element. Learn more about [proximity selectors](./selectors.md#css-extension-proximity).
-->
For convenience, selectors in the wrong format are heuristically converted to the right format:
- selector starting with `//` or `..` is assumed to be `xpath=selector`;

View File

@ -5460,7 +5460,9 @@ Playwright also supports the following CSS extensions:
* `:text("string")` - Matches elements that contain specific text node. Learn more about [text selector](./selectors.md#css-extension-text).
* `:visible` - Matches only visible elements. Learn more about [visible selector](./selectors.md#css-extension-visible).
* `:light(selector)` - Matches in the light DOM only as opposite to piercing open shadow roots. Learn more about [shadow piercing](./selectors.md#shadow-piercing).
<!--
* `:right-of(selector)`, `:left-of(selector)`, `:above(selector)`, `:below(selector)`, `:near(selector)`, `:within(selector)` - Match elements based on their relative position to another element. Learn more about [proximity selectors](./selectors.md#css-extension-proximity).
-->
For convenience, selectors in the wrong format are heuristically converted to the right format:
- selector starting with `//` or `..` is assumed to be `xpath=selector`;

View File

@ -213,6 +213,7 @@ await page.click('button:text("Sign in")');
await page.click(':light(.article > .header)');
```
<!--
#### CSS extension: proximity
Playwright provides a few proximity selectors based on the page layout. These can be combined with regular CSS for better results, for example `input:right-of(:text("Password"))` matches an input field that is to the right of text "Password".
@ -233,6 +234,7 @@ await page.fill('input:right-of(:text("Username"))');
// Click a button near the promo card.
await page.click('button:near(.promo-card)');
```
-->
### xpath

View File

@ -35,7 +35,7 @@ export function selectorsV2Enabled() {
}
export function selectorsV2EngineNames() {
return ['not', 'is', 'where', 'has', 'scope', 'light', 'visible', 'text-matches', 'text-is', 'above', 'below', 'right-of', 'left-of', 'near', 'within'];
return ['not', 'is', 'where', 'has', 'scope', 'light', 'visible', 'text-matches', 'text-is'];
}
export function parseSelector(selector: string, customNames: Set<string>): ParsedSelector {

View File

@ -60,12 +60,6 @@ export class SelectorEvaluatorImpl implements SelectorEvaluator {
this._engines.set('xpath', xpathEngine);
for (const attr of ['id', 'data-testid', 'data-test-id', 'data-test'])
this._engines.set(attr, createAttributeEngine(attr));
this._engines.set('right-of', createProximityEngine('right-of', boxRightOf));
this._engines.set('left-of', createProximityEngine('left-of', boxLeftOf));
this._engines.set('above', createProximityEngine('above', boxAbove));
this._engines.set('below', createProximityEngine('below', boxBelow));
this._engines.set('near', createProximityEngine('near', boxNear));
this._engines.set('within', createProximityEngine('within', boxWithin));
}
// This is the only function we should use for querying, because it does
@ -448,70 +442,6 @@ function createAttributeEngine(attr: string): SelectorEngine {
};
}
function areCloseRanges(from1: number, to1: number, from2: number, to2: number, threshold: number) {
return to1 >= from2 - threshold && to2 >= from1 - threshold;
}
function boxSize(box: DOMRect) {
return Math.sqrt(box.width * box.height);
}
function boxesProximityThreshold(box1: DOMRect, box2: DOMRect) {
return (boxSize(box1) + boxSize(box2)) / 2;
}
function boxRightOf(box1: DOMRect, box2: DOMRect): boolean {
// To the right, but not too far, and vertically intersects.
const distance = box1.left - box2.right;
return distance >= 0 && distance <= boxesProximityThreshold(box1, box2) &&
areCloseRanges(box1.top, box1.bottom, box2.top, box2.bottom, 0);
}
function boxLeftOf(box1: DOMRect, box2: DOMRect): boolean {
// To the left, but not too far, and vertically intersects.
const distance = box2.left - box1.right;
return distance >= 0 && distance <= boxesProximityThreshold(box1, box2) &&
areCloseRanges(box1.top, box1.bottom, box2.top, box2.bottom, 0);
}
function boxAbove(box1: DOMRect, box2: DOMRect): boolean {
// Above, but not too far, and horizontally intersects.
const distance = box2.top - box1.bottom;
return distance >= 0 && distance <= boxesProximityThreshold(box1, box2) &&
areCloseRanges(box1.left, box1.right, box2.left, box2.right, 0);
}
function boxBelow(box1: DOMRect, box2: DOMRect): boolean {
// Below, but not too far, and horizontally intersects.
const distance = box1.top - box2.bottom;
return distance >= 0 && distance <= boxesProximityThreshold(box1, box2) &&
areCloseRanges(box1.left, box1.right, box2.left, box2.right, 0);
}
function boxWithin(box1: DOMRect, box2: DOMRect): boolean {
return box1.left >= box2.left && box1.right <= box2.right && box1.top >= box2.top && box1.bottom <= box2.bottom;
}
function boxNear(box1: DOMRect, box2: DOMRect): boolean {
const intersects = !(box1.left >= box2.right || box2.left >= box1.right || box1.top >= box2.bottom || box2.top >= box1.bottom);
if (intersects)
return false;
const threshold = boxesProximityThreshold(box1, box2);
return areCloseRanges(box1.left, box1.right, box2.left, box2.right, threshold) &&
areCloseRanges(box1.top, box1.bottom, box2.top, box2.bottom, threshold);
}
function createProximityEngine(name: string, predicate: (box1: DOMRect, box2: DOMRect) => boolean): SelectorEngine {
return {
matches(element: Element, args: (string | number | Selector)[], context: QueryContext, evaluator: SelectorEvaluator): boolean {
if (!args.length)
throw new Error(`"${name}" engine expects a selector list`);
const box = element.getBoundingClientRect();
return evaluator.query(context, args).some(e => e !== element && predicate(box, e.getBoundingClientRect()));
},
};
}
export function parentElementOrShadowHost(element: Element): Element | undefined {
if (element.parentElement)
return element.parentElement;

View File

@ -52,7 +52,9 @@ it('should work with :visible', async ({page}) => {
expect(await page.$eval('div:visible', div => div.id)).toBe('target2');
});
it('should work with proximity selectors', async ({page}) => {
it('should work with proximity selectors', test => {
test.skip('Not ready yet');
}, async ({page}) => {
if (!selectorsV2Enabled())
return; // Selectors v1 do not support this.