fix(selectors): fix selector parsing for css attributes and quotes (#2389)

- css attribute selector may contain spaces;
- >> escaping inside strings was sometimes incorrect.

See added test cases for more details.
This commit is contained in:
Dmitry Gozman 2020-05-28 14:49:39 -07:00 committed by GitHub
parent 7981e4e3da
commit fdbd4fe197
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 3 deletions

View file

@ -219,6 +219,7 @@ function parentElementOrShadowHost(element: Element): Element | undefined {
function split(selector: string): string[][] {
let index = 0;
let quote: string | undefined;
let insideAttr = false;
let start = 0;
let space: 'none' | 'before' | 'after' = 'none';
const result: string[][] = [];
@ -235,7 +236,7 @@ function split(selector: string): string[][] {
};
while (index < selector.length) {
const c = selector[index];
if (!quote && c === ' ') {
if (!quote && !insideAttr && c === ' ') {
if (space === 'none' || space === 'before')
space = 'before';
index++;
@ -256,10 +257,16 @@ function split(selector: string): string[][] {
} else if (c === quote) {
quote = undefined;
index++;
} else if (c === '\'' || c === '"') {
} else if (!quote && (c === '\'' || c === '"')) {
quote = c;
index++;
} else if (!quote && c === ',') {
} else if (!quote && c === '[') {
insideAttr = true;
index++;
} else if (!quote && insideAttr && c === ']') {
insideAttr = false;
index++;
} else if (!quote && !insideAttr && c === ',') {
appendToResult();
index++;
start = index;

View file

@ -219,6 +219,9 @@ export function parseSelector(selector: string): types.ParsedSelector {
} else if (c === quote) {
quote = undefined;
index++;
} else if (!quote && (c === '"' || c === '\'' || c === '`')) {
quote = c;
index++;
} else if (!quote && c === '>' && selector[index + 1] === '>') {
append();
index += 2;

View file

@ -494,6 +494,14 @@ describe('text selector', () => {
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div> " </div>');
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div> \' </div>');
await page.setContent(`<div>Hi''&gt;&gt;foo=bar</div>`);
expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi''&gt;&gt;foo=bar</div>`);
await page.setContent(`<div>Hi'"&gt;&gt;foo=bar</div>`);
expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi'"&gt;&gt;foo=bar</div>`);
await page.setContent(`<div>Hi&gt;&gt;<span></span></div>`);
expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(`<span></span>`);
await page.setContent(`<div>a<br>b</div><div>a</div>`);
expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
@ -622,6 +630,30 @@ describe('css selector', () => {
expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe('<span></span>');
});
it('should work with attribute selectors', async({page}) => {
await page.setContent(`<div attr="hello world" attr2="hello-''>>foo=bar[]" attr3="] span"><span></span></div>`);
await page.evaluate(() => window.div = document.querySelector('div'));
const selectors = [
`[attr="hello world"]`,
`[attr = "hello world"]`,
`[attr ~= world]`,
`[attr ^=hello ]`,
`[attr $= world ]`,
`[attr *= "llo wor" ]`,
`[attr2 |= hello]`,
`[attr = "Hello World" i ]`,
`[attr *= "llo WOR"i]`,
`[attr $= woRLD i]`,
`[attr2 = "hello-''>>foo=bar[]"]`,
`[attr2 $="foo=bar[]"]`,
];
for (const selector of selectors)
expect(await page.$eval(selector, e => e === div)).toBe(true);
expect(await page.$eval(`[attr*=hello] span`, e => e.parentNode === div)).toBe(true);
expect(await page.$eval(`[attr*=hello] >> span`, e => e.parentNode === div)).toBe(true);
expect(await page.$eval(`[attr3="] span"] >> span`, e => e.parentNode === div)).toBe(true);
});
});
describe('attribute selector', () => {