fix(css selector): absolutize relative CSS selectors (#9088)

Selectors like `> div` are replaced by `:scope > div`,
which is useful for combining them with parent selectors.
This is a part of CSS Level 4 spec.
This commit is contained in:
Dmitry Gozman 2021-09-22 14:13:00 -07:00 committed by GitHub
parent 79eb7744bc
commit f0d23b5d4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 17 additions and 3 deletions

View File

@ -133,8 +133,14 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
}
function consumeComplexSelector(): CSSComplexSelector {
const result: CSSComplexSelector = { simples: [] };
skipWhitespace();
const result = { simples: [{ selector: consumeSimpleSelector(), combinator: '' as ClauseCombinator }] };
if (isClauseCombinator()) {
// Put implicit ":scope" at the start. https://drafts.csswg.org/selectors-4/#absolutize
result.simples.push({ selector: { functions: [{ name: 'scope', args: [] }] }, combinator: '' });
} else {
result.simples.push({ selector: consumeSimpleSelector(), combinator: '' });
}
while (true) {
skipWhitespace();
if (isClauseCombinator()) {

View File

@ -156,7 +156,6 @@ test('should support slowmo option', async ({browserType, startRemoteServer}) =>
const start = Date.now();
await browser1.newContext();
await browser1.close();
console.log(Date.now() - start);
expect(Date.now() - start).toBeGreaterThan(199);
});

View File

@ -18,7 +18,7 @@ import { playwrightTest as it, expect } from './config/browserTest';
import { parseCSS, serializeSelector as serialize } from '../src/server/common/cssParser';
const parse = (selector: string) => {
return parseCSS(selector, new Set(['text', 'not', 'has', 'react', 'scope', 'right-of', 'scope', 'is'])).selector;
return parseCSS(selector, new Set(['text', 'not', 'has', 'react', 'scope', 'right-of', 'is'])).selector;
};
it('should parse css', async () => {
@ -48,6 +48,7 @@ it('should parse css', async () => {
expect(serialize(parse('div~ span'))).toBe('div ~ span');
expect(serialize(parse('div >.class #id+ span'))).toBe('div > .class #id + span');
expect(serialize(parse('div>span+.class'))).toBe('div > span + .class');
expect(serialize(parse('>span'))).toBe(':scope() > span');
expect(serialize(parse('div:not(span)'))).toBe('div:not(span)');
expect(serialize(parse(':not(span)#id'))).toBe('#id:not(span)');

View File

@ -385,6 +385,14 @@ it('should work with :scope', async ({page, server}) => {
}
});
it('should absolutize relative selectors', async ({page, server}) => {
await page.setContent(`<div><span>Hi</span></div>`);
expect(await page.$eval('div >> >span', e => e.textContent)).toBe('Hi');
expect(await page.locator('div').locator('>span').textContent()).toBe('Hi');
expect(await page.$eval('div:has(> span)', e => e.outerHTML)).toBe('<div><span>Hi</span></div>');
expect(await page.$('div:has(> div)')).toBe(null);
});
it('css on the handle should be relative', async ({ page }) => {
await page.setContent(`
<span class="find-me" id=target1>1</span>