From f0d23b5d4dd632410af33ac445b9e71d814507e3 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 22 Sep 2021 14:13:00 -0700 Subject: [PATCH] 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. --- src/server/common/cssParser.ts | 8 +++++++- tests/browsertype-connect.spec.ts | 1 - tests/css-parser.spec.ts | 3 ++- tests/page/selectors-css.spec.ts | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/server/common/cssParser.ts b/src/server/common/cssParser.ts index d54c894ee7..62edb9069d 100644 --- a/src/server/common/cssParser.ts +++ b/src/server/common/cssParser.ts @@ -133,8 +133,14 @@ export function parseCSS(selector: string, customNames: Set): { 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()) { diff --git a/tests/browsertype-connect.spec.ts b/tests/browsertype-connect.spec.ts index 339ef0f54f..66c1ddf0ae 100644 --- a/tests/browsertype-connect.spec.ts +++ b/tests/browsertype-connect.spec.ts @@ -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); }); diff --git a/tests/css-parser.spec.ts b/tests/css-parser.spec.ts index 33ba62aae3..771aa1e1e5 100644 --- a/tests/css-parser.spec.ts +++ b/tests/css-parser.spec.ts @@ -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)'); diff --git a/tests/page/selectors-css.spec.ts b/tests/page/selectors-css.spec.ts index d568de6db7..d9b6a19b12 100644 --- a/tests/page/selectors-css.spec.ts +++ b/tests/page/selectors-css.spec.ts @@ -385,6 +385,14 @@ it('should work with :scope', async ({page, server}) => { } }); +it('should absolutize relative selectors', async ({page, server}) => { + await page.setContent(`
Hi
`); + 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('
Hi
'); + expect(await page.$('div:has(> div)')).toBe(null); +}); + it('css on the handle should be relative', async ({ page }) => { await page.setContent(` 1