fix(inspector): highlight xpath/css locators without engine prefix (#27742)
Motivation: As of today when a user inspects a Locator which is a xpath,
it won't work if the user has not prefixed it with `xpath=` because we
internally compare the given with the generated locator.
Works: `locator('xpath=//div[contains(@class, "foo")]')`
Does not work: `locator('//div[contains(@class, "foo")]')`
Relates
https://github.com/microsoft/playwright/issues/27707#issue-1952360264
Fixes
https://github.com/microsoft/playwright-dotnet/issues/2718#issuecomment-1771073816
---------
Signed-off-by: Max Schmitt <max@schmitt.mx>
This commit is contained in:
parent
9af667be26
commit
f48861ddee
|
|
@ -204,7 +204,14 @@ function innerAsLocators(factory: LocatorFactory, parsed: ParsedSelector, isFram
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.push([locatorPart]);
|
// Selectors can be prefixed with engine name, e.g. xpath=//foo
|
||||||
|
let locatorPartWithEngine: string | undefined;
|
||||||
|
if (['xpath', 'css'].includes(part.name)) {
|
||||||
|
const selectorPart = stringifySelector({ parts: [part] }, /* forceEngineName */ true);
|
||||||
|
locatorPartWithEngine = factory.generateLocator(base, locatorType, selectorPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push([locatorPart, locatorPartWithEngine].filter(Boolean) as string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return combineTokens(factory, tokens, maxOutputSize);
|
return combineTokens(factory, tokens, maxOutputSize);
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,18 @@ function selectorPartsEqual(list1: ParsedSelectorPart[], list2: ParsedSelectorPa
|
||||||
return stringifySelector({ parts: list1 }) === stringifySelector({ parts: list2 });
|
return stringifySelector({ parts: list1 }) === stringifySelector({ parts: list2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringifySelector(selector: string | ParsedSelector): string {
|
export function stringifySelector(selector: string | ParsedSelector, forceEngineName?: boolean): string {
|
||||||
if (typeof selector === 'string')
|
if (typeof selector === 'string')
|
||||||
return selector;
|
return selector;
|
||||||
return selector.parts.map((p, i) => {
|
return selector.parts.map((p, i) => {
|
||||||
const prefix = p.name === 'css' ? '' : p.name + '=';
|
let includeEngine = true;
|
||||||
|
if (!forceEngineName && i !== selector.capture) {
|
||||||
|
if (p.name === 'css')
|
||||||
|
includeEngine = false;
|
||||||
|
else if (p.name === 'xpath' && p.source.startsWith('//') || p.source.startsWith('..'))
|
||||||
|
includeEngine = false;
|
||||||
|
}
|
||||||
|
const prefix = includeEngine ? p.name + '=' : '';
|
||||||
return `${i === selector.capture ? '*' : ''}${prefix}${p.source}`;
|
return `${i === selector.capture ? '*' : ''}${prefix}${p.source}`;
|
||||||
}).join(' >> ');
|
}).join(' >> ');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -503,10 +503,20 @@ it('asLocator internal:chain', async () => {
|
||||||
|
|
||||||
it('asLocator xpath', async () => {
|
it('asLocator xpath', async () => {
|
||||||
const selector = `//*[contains(normalizer-text(), 'foo']`;
|
const selector = `//*[contains(normalizer-text(), 'foo']`;
|
||||||
expect.soft(asLocator('javascript', selector, false)).toBe(`locator('xpath=//*[contains(normalizer-text(), \\'foo\\']')`);
|
expect.soft(asLocator('javascript', selector, false)).toBe(`locator('//*[contains(normalizer-text(), \\'foo\\']')`);
|
||||||
expect.soft(asLocator('python', selector, false)).toBe(`locator(\"xpath=//*[contains(normalizer-text(), 'foo']\")`);
|
expect.soft(asLocator('python', selector, false)).toBe(`locator(\"//*[contains(normalizer-text(), 'foo']\")`);
|
||||||
expect.soft(asLocator('java', selector, false)).toBe(`locator(\"xpath=//*[contains(normalizer-text(), 'foo']\")`);
|
expect.soft(asLocator('java', selector, false)).toBe(`locator(\"//*[contains(normalizer-text(), 'foo']\")`);
|
||||||
expect.soft(asLocator('csharp', selector, false)).toBe(`Locator(\"xpath=//*[contains(normalizer-text(), 'foo']\")`);
|
expect.soft(asLocator('csharp', selector, false)).toBe(`Locator(\"//*[contains(normalizer-text(), 'foo']\")`);
|
||||||
|
expect.soft(parseLocator('javascript', `locator('//*[contains(normalizer-text(), \\'foo\\']')`, 'data-testid')).toBe("//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('javascript', `locator("//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('javascript', `locator('xpath=//*[contains(normalizer-text(), \\'foo\\']')`, 'data-testid')).toBe("xpath=//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('javascript', `locator("xpath=//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("xpath=//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('python', `locator("//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('python', `locator("xpath=//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("xpath=//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('java', `locator("//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('java', `locator("xpath=//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("xpath=//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('csharp', `Locator("//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("//*[contains(normalizer-text(), 'foo']");
|
||||||
|
expect.soft(parseLocator('csharp', `Locator("xpath=//*[contains(normalizer-text(), 'foo']")`, 'data-testid')).toBe("xpath=//*[contains(normalizer-text(), 'foo']");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parseLocator quotes', async () => {
|
it('parseLocator quotes', async () => {
|
||||||
|
|
@ -521,6 +531,17 @@ it('parseLocator quotes', async () => {
|
||||||
expect.soft(parseLocator('csharp', `Locator('text="bar"')`, '')).toBe(``);
|
expect.soft(parseLocator('csharp', `Locator('text="bar"')`, '')).toBe(``);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('parseLocator css', async () => {
|
||||||
|
expect.soft(parseLocator('javascript', `locator('.foo')`, '')).toBe(`.foo`);
|
||||||
|
expect.soft(parseLocator('javascript', `locator('css=.foo')`, '')).toBe(`css=.foo`);
|
||||||
|
expect.soft(parseLocator('python', `locator(".foo")`, '')).toBe(`.foo`);
|
||||||
|
expect.soft(parseLocator('python', `locator("css=.foo")`, '')).toBe(`css=.foo`);
|
||||||
|
expect.soft(parseLocator('java', `locator(".foo")`, '')).toBe(`.foo`);
|
||||||
|
expect.soft(parseLocator('java', `locator("css=.foo")`, '')).toBe(`css=.foo`);
|
||||||
|
expect.soft(parseLocator('csharp', `Locator(".foo")`, '')).toBe(`.foo`);
|
||||||
|
expect.soft(parseLocator('csharp', `Locator("css=.foo")`, '')).toBe(`css=.foo`);
|
||||||
|
});
|
||||||
|
|
||||||
it('parse locators strictly', () => {
|
it('parse locators strictly', () => {
|
||||||
const selector = 'div >> internal:has-text=\"Goodbye world\"i >> span';
|
const selector = 'div >> internal:has-text=\"Goodbye world\"i >> span';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ it('should respect timeout xpath', async ({ page, playwright }) => {
|
||||||
await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
|
await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
|
expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
|
||||||
expect(error.message).toContain('waiting for locator(\'xpath=//div\')');
|
expect(error.message).toContain('waiting for locator(\'//div\')');
|
||||||
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue