mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 13:50:25 +03:00
fix(css parser): support nested builtin functions (#27841)
Things like `:nth-child(1 of :has(span:nth-last-child(3)))`. Fixes #27743.
This commit is contained in:
parent
88f30d1ce2
commit
100d3b2601
@ -98,10 +98,18 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||
return tokens[p] instanceof css.CommaToken;
|
||||
}
|
||||
|
||||
function isOpenParen(p = pos) {
|
||||
return tokens[p] instanceof css.OpenParenToken;
|
||||
}
|
||||
|
||||
function isCloseParen(p = pos) {
|
||||
return tokens[p] instanceof css.CloseParenToken;
|
||||
}
|
||||
|
||||
function isFunction(p = pos) {
|
||||
return tokens[p] instanceof css.FunctionToken;
|
||||
}
|
||||
|
||||
function isStar(p = pos) {
|
||||
return (tokens[p] instanceof css.DelimToken) && tokens[p].value === '*';
|
||||
}
|
||||
@ -186,7 +194,7 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||
functions.push({ name, args: [] });
|
||||
names.add(name);
|
||||
}
|
||||
} else if (tokens[pos] instanceof css.FunctionToken) {
|
||||
} else if (isFunction()) {
|
||||
const name = (tokens[pos++].value as string).toLowerCase();
|
||||
if (!customNames.has(name)) {
|
||||
rawCSSString += `:${name}(${consumeBuiltinFunctionArguments()})`;
|
||||
@ -221,14 +229,22 @@ export function parseCSS(selector: string, customNames: Set<string>): { selector
|
||||
|
||||
function consumeBuiltinFunctionArguments(): string {
|
||||
let s = '';
|
||||
while (!isCloseParen() && !isEOF())
|
||||
let balance = 1; // First open paren is a part of a function token.
|
||||
while (!isEOF()) {
|
||||
if (isOpenParen() || isFunction())
|
||||
balance++;
|
||||
if (isCloseParen())
|
||||
balance--;
|
||||
if (!balance)
|
||||
break;
|
||||
s += tokens[pos++].toSource();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
const result = consumeFunctionArguments();
|
||||
if (!isEOF())
|
||||
throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
|
||||
throw unexpected();
|
||||
if (result.some(arg => typeof arg !== 'object' || !('simples' in arg)))
|
||||
throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
|
||||
return { selector: result as CSSComplexSelector[], names: Array.from(names) };
|
||||
|
@ -274,6 +274,30 @@ it('should work with :nth-child', async ({ page, server }) => {
|
||||
expect(await page.$$eval(`css=span:nth-child(23n+2)`, els => els.length)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with :nth-child(of) notation with nested functions', async ({ page, browserName }) => {
|
||||
it.fixme(browserName === 'firefox', 'Should enable once Firefox supports this syntax');
|
||||
|
||||
await page.setContent(`
|
||||
<div>
|
||||
<span>span1</span>
|
||||
<span class=foo>span2<dd></dd></span>
|
||||
<span class=foo>span3<dd class=marker></dd></span>
|
||||
<span class=foo>span4<dd class=marker></dd></span>
|
||||
<span class=foo>span5<dd></dd></span>
|
||||
<span>span6</span>
|
||||
</div>
|
||||
`);
|
||||
expect(await page.$$eval(`css=span:nth-child(1)`, els => els.map(e => e.textContent))).toEqual(['span1']);
|
||||
expect(await page.$$eval(`css=span:nth-child(1 of .foo)`, els => els.map(e => e.textContent))).toEqual(['span2']);
|
||||
expect(await page.$$eval(`css=span:nth-child(1 of .foo:has(dd.marker))`, els => els.map(e => e.textContent))).toEqual(['span3']);
|
||||
expect(await page.$$eval(`css=span:nth-last-child(1 of .foo:has(dd.marker))`, els => els.map(e => e.textContent))).toEqual(['span4']);
|
||||
expect(await page.$$eval(`css=span:nth-last-child(1 of .foo)`, els => els.map(e => e.textContent))).toEqual(['span5']);
|
||||
expect(await page.$$eval(`css=span:nth-last-child( 1 )`, els => els.map(e => e.textContent))).toEqual(['span6']);
|
||||
|
||||
expect(await page.$$eval(`css=span:nth-child(1 of .foo:nth-child(3))`, els => els.map(e => e.textContent))).toEqual(['span3']);
|
||||
expect(await page.$$eval(`css=span:nth-child(1 of .foo:nth-child(6))`, els => els.map(e => e.textContent))).toEqual([]);
|
||||
});
|
||||
|
||||
it('should work with :not', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/deep-shadow.html');
|
||||
expect(await page.$$eval(`css=div:not(#root1)`, els => els.length)).toBe(2);
|
||||
|
Loading…
Reference in New Issue
Block a user