Skip to content

Commit

Permalink
feat(selectors): disable proximity selectors (#4659)
Browse files Browse the repository at this point in the history
These are not ready for prime time yet.
  • Loading branch information
dgozman authored Dec 10, 2020
1 parent 84ff20f commit c8e9b05
Show file tree
Hide file tree
Showing 6 changed files with 10 additions and 72 deletions.
2 changes: 2 additions & 0 deletions docs-src/api-footer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down
2 changes: 2 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down
2 changes: 2 additions & 0 deletions docs/selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/server/common/selectorParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
70 changes: 0 additions & 70 deletions src/server/injected/selectorEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion test/selectors-misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

0 comments on commit c8e9b05

Please sign in to comment.