diff --git a/src/server/common/selectorParser.ts b/src/server/common/selectorParser.ts index b5e75c544b..c2cda46821 100644 --- a/src/server/common/selectorParser.ts +++ b/src/server/common/selectorParser.ts @@ -54,7 +54,7 @@ export function parseSelector(selector: string, customNames: Set): Parse }; } - const chain = (from: number, to: number): CSSComplexSelector => { + const chain = (from: number, to: number, turnFirstTextIntoScope: boolean): CSSComplexSelector => { const result: CSSComplexSelector = { simples: [] }; for (const part of v1.parts.slice(from, to)) { let name = part.name; @@ -76,6 +76,8 @@ export function parseSelector(selector: string, customNames: Set): Parse } } else if (name === 'text') { let simple = textSelectorToSimple(part.body); + if (turnFirstTextIntoScope) + simple.functions.push({ name: 'is', args: [ simpleToComplex(callWith('scope', [])), simpleToComplex({ css: '*', functions: [] }) ]}); if (result.simples.length) result.simples[result.simples.length - 1].combinator = '>='; if (wrapInLight) @@ -87,14 +89,16 @@ export function parseSelector(selector: string, customNames: Set): Parse simple = callWith('light', [simpleToComplex(simple)]); result.simples.push({ selector: simple, combinator: '' }); } + if (name !== 'text') + turnFirstTextIntoScope = false; } return result; }; const capture = v1.capture === undefined ? v1.parts.length - 1 : v1.capture; - const result = chain(0, capture + 1); + const result = chain(0, capture + 1, false); if (capture + 1 < v1.parts.length) { - const has = chain(capture + 1, v1.parts.length); + const has = chain(capture + 1, v1.parts.length, true); const last = result.simples[result.simples.length - 1]; last.selector.functions.push({ name: 'has', args: [has] }); } diff --git a/src/server/injected/selectorEvaluator.ts b/src/server/injected/selectorEvaluator.ts index 4b715acd2d..245fef8f50 100644 --- a/src/server/injected/selectorEvaluator.ts +++ b/src/server/injected/selectorEvaluator.ts @@ -120,8 +120,8 @@ export class SelectorEvaluatorImpl implements SelectorEvaluator { private _matchesSimple(element: Element, simple: CSSSimpleSelector, context: QueryContext): boolean { return this._cached(this._cacheMatchesSimple, element, [simple, context], () => { - const isScopeClause = simple.functions.some(f => f.name === 'scope'); - if (!isScopeClause && element === context.scope) + const isPossiblyScopeClause = simple.functions.some(f => f.name === 'scope' || f.name === 'is'); + if (!isPossiblyScopeClause && element === context.scope) return false; if (simple.css && !this._matchesCSS(element, simple.css)) return false; diff --git a/test/selectors-css.spec.ts b/test/selectors-css.spec.ts index a8b08ce860..8150e74669 100644 --- a/test/selectors-css.spec.ts +++ b/test/selectors-css.spec.ts @@ -348,6 +348,8 @@ it('should work with :is', async ({page, server}) => { expect(await page.$$eval(`css=:is(div, span)`, els => els.length)).toBe(7); expect(await page.$$eval(`css=section:is(section) div:is(section div)`, els => els.length)).toBe(3); expect(await page.$$eval(`css=:is(div, span) > *`, els => els.length)).toBe(6); + expect(await page.$$eval(`css=#root1:has(:is(#root1))`, els => els.length)).toBe(0); + expect(await page.$$eval(`css=#root1:has(:is(:scope, #root1))`, els => els.length)).toBe(1); }); it('should work with :has', async ({page, server}) => { diff --git a/test/selectors-text.spec.ts b/test/selectors-text.spec.ts index c64f966093..98d8a36428 100644 --- a/test/selectors-text.spec.ts +++ b/test/selectors-text.spec.ts @@ -227,3 +227,8 @@ it('should match root after >>', async ({page, server}) => { const element2 = await page.$('text=test >> text=test'); expect(element2).toBeTruthy(); }); + +it('should match root after >> with *', async ({ page }) => { + await page.setContent(` `); + expect(await page.$$eval('*css=button >> text=hello >> text=world', els => els.length)).toBe(2); +});