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:
parent
7981e4e3da
commit
fdbd4fe197
|
|
@ -219,6 +219,7 @@ function parentElementOrShadowHost(element: Element): Element | undefined {
|
||||||
function split(selector: string): string[][] {
|
function split(selector: string): string[][] {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let quote: string | undefined;
|
let quote: string | undefined;
|
||||||
|
let insideAttr = false;
|
||||||
let start = 0;
|
let start = 0;
|
||||||
let space: 'none' | 'before' | 'after' = 'none';
|
let space: 'none' | 'before' | 'after' = 'none';
|
||||||
const result: string[][] = [];
|
const result: string[][] = [];
|
||||||
|
|
@ -235,7 +236,7 @@ function split(selector: string): string[][] {
|
||||||
};
|
};
|
||||||
while (index < selector.length) {
|
while (index < selector.length) {
|
||||||
const c = selector[index];
|
const c = selector[index];
|
||||||
if (!quote && c === ' ') {
|
if (!quote && !insideAttr && c === ' ') {
|
||||||
if (space === 'none' || space === 'before')
|
if (space === 'none' || space === 'before')
|
||||||
space = 'before';
|
space = 'before';
|
||||||
index++;
|
index++;
|
||||||
|
|
@ -256,10 +257,16 @@ function split(selector: string): string[][] {
|
||||||
} else if (c === quote) {
|
} else if (c === quote) {
|
||||||
quote = undefined;
|
quote = undefined;
|
||||||
index++;
|
index++;
|
||||||
} else if (c === '\'' || c === '"') {
|
} else if (!quote && (c === '\'' || c === '"')) {
|
||||||
quote = c;
|
quote = c;
|
||||||
index++;
|
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();
|
appendToResult();
|
||||||
index++;
|
index++;
|
||||||
start = index;
|
start = index;
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@ export function parseSelector(selector: string): types.ParsedSelector {
|
||||||
} else if (c === quote) {
|
} else if (c === quote) {
|
||||||
quote = undefined;
|
quote = undefined;
|
||||||
index++;
|
index++;
|
||||||
|
} else if (!quote && (c === '"' || c === '\'' || c === '`')) {
|
||||||
|
quote = c;
|
||||||
|
index++;
|
||||||
} else if (!quote && c === '>' && selector[index + 1] === '>') {
|
} else if (!quote && c === '>' && selector[index + 1] === '>') {
|
||||||
append();
|
append();
|
||||||
index += 2;
|
index += 2;
|
||||||
|
|
|
||||||
|
|
@ -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>');
|
||||||
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''>>foo=bar</div>`);
|
||||||
|
expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi''>>foo=bar</div>`);
|
||||||
|
await page.setContent(`<div>Hi'">>foo=bar</div>`);
|
||||||
|
expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi'">>foo=bar</div>`);
|
||||||
|
|
||||||
|
await page.setContent(`<div>Hi>><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>`);
|
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=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>');
|
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=[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>');
|
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', () => {
|
describe('attribute selector', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue