mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 00:52:05 +03:00
chore: add explore locator parser (#18429)
This commit is contained in:
parent
2d07c10888
commit
2c3fa1b1ff
@ -25,6 +25,7 @@ import { Recorder } from './recorder';
|
||||
import { EmptyRecorderApp } from './recorder/recorderApp';
|
||||
import { asLocator } from './isomorphic/locatorGenerators';
|
||||
import type { Language } from './isomorphic/locatorGenerators';
|
||||
import { locatorOrSelectorAsSelector } from './isomorphic/locatorParser';
|
||||
|
||||
const internalMetadata = serverSideCallMetadata();
|
||||
|
||||
@ -138,6 +139,7 @@ export class DebugController extends SdkObject {
|
||||
}
|
||||
|
||||
async highlight(selector: string) {
|
||||
selector = locatorOrSelectorAsSelector(selector);
|
||||
for (const recorder of await this._allRecorders())
|
||||
recorder.setHighlightedSelector(selector);
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||
private toCallWithExact(method: string, body: string | RegExp, exact: boolean) {
|
||||
if (isRegExp(body)) {
|
||||
const suffix = body.flags.includes('i') ? ', re.IGNORECASE' : '';
|
||||
return `${method}(re.compile(r${this.quote(body.source)}${suffix}))`;
|
||||
return `${method}(re.compile(r"${body.source.replace(/\\\//, '/').replace(/"/g, '\\"')}"${suffix}))`;
|
||||
}
|
||||
if (exact)
|
||||
return `${method}(${this.quote(body)}, exact=true)`;
|
||||
@ -216,7 +216,7 @@ export class PythonLocatorFactory implements LocatorFactory {
|
||||
private toHasText(body: string | RegExp) {
|
||||
if (isRegExp(body)) {
|
||||
const suffix = body.flags.includes('i') ? ', re.IGNORECASE' : '';
|
||||
return `re.compile(r${this.quote(body.source)}${suffix})`;
|
||||
return `re.compile(r"${body.source.replace(/\\\//, '/').replace(/"/g, '\\"')}"${suffix})`;
|
||||
}
|
||||
return `${this.quote(body)}`;
|
||||
}
|
||||
@ -274,7 +274,7 @@ export class JavaLocatorFactory implements LocatorFactory {
|
||||
return `${method}(Pattern.compile(${this.quote(body.source)}${suffix}))`;
|
||||
}
|
||||
if (exact)
|
||||
return `${method}(${this.quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(exact))`;
|
||||
return `${method}(${this.quote(body)}, new ${clazz}.${toTitleCase(method)}Options().setExact(true))`;
|
||||
return `${method}(${this.quote(body)})`;
|
||||
}
|
||||
|
||||
|
135
packages/playwright-core/src/server/isomorphic/locatorParser.ts
Normal file
135
packages/playwright-core/src/server/isomorphic/locatorParser.ts
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { escapeForAttributeSelector, escapeForTextSelector } from '../../utils/isomorphic/stringUtils';
|
||||
import { parseSelector } from './selectorParser';
|
||||
|
||||
export function parseLocator(locator: string): string {
|
||||
locator = locator
|
||||
.replace(/AriaRole\s*\.\s*([\w]+)/g, (_, group) => group.toLowerCase())
|
||||
.replace(/(get_by_role|getByRole)\s*\(\s*(?:["'`])([^'"`]+)['"`]/g, (_, group1, group2) => `${group1}(${group2.toLowerCase()}`);
|
||||
const params: { quote: string, text: string }[] = [];
|
||||
let template = '';
|
||||
for (let i = 0; i < locator.length; ++i) {
|
||||
const quote = locator[i];
|
||||
if (quote !== '"' && quote !== '\'' && quote !== '`' && quote !== '/') {
|
||||
template += quote;
|
||||
continue;
|
||||
}
|
||||
const isRegexEscaping = locator[i - 1] === 'r' || locator[i] === '/';
|
||||
++i;
|
||||
let text = '';
|
||||
while (i < locator.length) {
|
||||
if (locator[i] === '\\') {
|
||||
if (isRegexEscaping) {
|
||||
if (locator[i + 1] !== quote)
|
||||
text += locator[i];
|
||||
++i;
|
||||
text += locator[i];
|
||||
} else {
|
||||
++i;
|
||||
if (locator[i] === 'n')
|
||||
text += '\n';
|
||||
else if (locator[i] === 'r')
|
||||
text += '\r';
|
||||
else if (locator[i] === 't')
|
||||
text += '\t';
|
||||
else
|
||||
text += locator[i];
|
||||
}
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (locator[i] !== quote) {
|
||||
text += locator[i++];
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
params.push({ quote, text });
|
||||
template += (quote === '/' ? 'r' : '') + '$' + params.length;
|
||||
}
|
||||
|
||||
// Equalize languages.
|
||||
template = template.toLowerCase()
|
||||
.replace(/get_by_alt_text/g, 'getbyalttext')
|
||||
.replace(/get_by_test_id/g, 'getbytestid')
|
||||
.replace(/get_by_([\w]+)/g, 'getby$1')
|
||||
.replace(/has_text/g, 'hastext')
|
||||
.replace(/[{}\s]/g, '')
|
||||
.replace(/new\(\)/g, '')
|
||||
.replace(/new[\w]+\.[\w]+options\(\)/g, '')
|
||||
.replace(/\.set([\w]+)\(([^)]+)\)/g, (_, group1, group2) => ',' + group1.toLowerCase() + '=' + group2.toLowerCase())
|
||||
.replace(/:/g, '=')
|
||||
.replace(/,re\.ignorecase/g, 'i')
|
||||
.replace(/,pattern.case_insensitive/g, 'i')
|
||||
.replace(/,regexoptions.ignorecase/g, 'i')
|
||||
.replace(/re.compile\(([^)]+)\)/g, '$1') // Python has regex strings as r"foo"
|
||||
.replace(/pattern.compile\(([^)]+)\)/g, 'r$1')
|
||||
.replace(/newregex\(([^)]+)\)/g, 'r$1')
|
||||
.replace(/string=/g, '=')
|
||||
.replace(/regex=/g, '=')
|
||||
.replace(/,,/g, ',');
|
||||
|
||||
// Transform.
|
||||
template = template
|
||||
.replace(/locator\(([^)]+)\)/g, '$1')
|
||||
.replace(/getbyrole\(([^)]+)\)/g, 'internal:role=$1')
|
||||
.replace(/getbytext\(([^)]+)\)/g, 'internal:text=$1')
|
||||
.replace(/getbylabel\(([^)]+)\)/g, 'internal:label=$1')
|
||||
.replace(/getbytestid\(([^)]+)\)/g, 'internal:attr=[data-testid=$1s]')
|
||||
.replace(/getby(placeholder|alt|title)(?:text)?\(([^)]+)\)/g, 'internal:attr=[$1=$2]')
|
||||
.replace(/first(\(\))?/g, 'nth=0')
|
||||
.replace(/last(\(\))?/g, 'nth=-1')
|
||||
.replace(/nth\(([^)]+)\)/g, 'nth=$1')
|
||||
.replace(/filter\(.*hastext=([^)]+)\)/g, 'internal:has-text=$1')
|
||||
.replace(/,exact=false/g, '')
|
||||
.replace(/,exact=true/g, 's')
|
||||
.replace(/\,/g, '][');
|
||||
|
||||
return template.split('.').map(t => {
|
||||
if (!t.startsWith('internal:'))
|
||||
return t.replace(/\$(\d+)/g, (_, ordinal) => { const param = params[+ordinal - 1]; return param.text; });
|
||||
t = t.includes('[') ? t.replace(/\]/, '') + ']' : t;
|
||||
t = t
|
||||
.replace(/(?:r)\$(\d+)(i)?/g, (_, ordinal, suffix) => {
|
||||
const param = params[+ordinal - 1];
|
||||
if (t.startsWith('internal:attr') || t.startsWith('internal:role'))
|
||||
return new RegExp(param.text) + (suffix || '');
|
||||
return escapeForTextSelector(new RegExp(param.text, suffix), false);
|
||||
})
|
||||
.replace(/\$(\d+)(i|s)?/g, (_, ordinal, suffix) => {
|
||||
const param = params[+ordinal - 1];
|
||||
if (t.startsWith('internal:attr') || t.startsWith('internal:role'))
|
||||
return escapeForAttributeSelector(param.text, suffix === 's');
|
||||
return escapeForTextSelector(param.text, suffix === 's');
|
||||
});
|
||||
return t;
|
||||
}).join(' >> ');
|
||||
}
|
||||
|
||||
export function locatorOrSelectorAsSelector(locator: string): string {
|
||||
try {
|
||||
parseSelector(locator);
|
||||
return locator;
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
return parseLocator(locator);
|
||||
} catch (e) {
|
||||
}
|
||||
return locator;
|
||||
}
|
@ -41,6 +41,7 @@ import { Debugger } from './debugger';
|
||||
import { EventEmitter } from 'events';
|
||||
import { raceAgainstTimeout } from '../utils/timeoutRunner';
|
||||
import type { Language, LanguageGenerator } from './recorder/language';
|
||||
import { locatorOrSelectorAsSelector } from './isomorphic/locatorParser';
|
||||
|
||||
type BindingSource = { frame: Frame, page: Page };
|
||||
|
||||
@ -210,7 +211,7 @@ export class Recorder implements InstrumentationListener {
|
||||
}
|
||||
|
||||
setHighlightedSelector(selector: string) {
|
||||
this._highlightedSelector = selector;
|
||||
this._highlightedSelector = locatorOrSelectorAsSelector(selector);
|
||||
this._refreshOverlay();
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ function cssEscapeOne(s: string, i: number): string {
|
||||
export function escapeForTextSelector(text: string | RegExp, exact: boolean): string {
|
||||
if (typeof text !== 'string')
|
||||
return String(text);
|
||||
return `${JSON.stringify(text)}${exact ? '' : 'i'}`;
|
||||
return `${JSON.stringify(text)}${exact ? 's' : 'i'}`;
|
||||
}
|
||||
|
||||
export function escapeForAttributeSelector(value: string, exact: boolean): string {
|
||||
@ -69,5 +69,5 @@ export function escapeForAttributeSelector(value: string, exact: boolean): strin
|
||||
// cssEscape(value).replace(/\\ /g, ' ')
|
||||
// However, our attribute selectors do not conform to CSS parsing spec,
|
||||
// so we escape them differently.
|
||||
return `"${value.replace(/["]/g, '\\"')}"${exact ? '' : 'i'}`;
|
||||
return `"${value.replace(/["]/g, '\\"')}"${exact ? 's' : 'i'}`;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' } }).catch(() => { });
|
||||
}}>Explore</ToolbarButton>
|
||||
<input ref={selectorInputRef} className='selector-input' placeholder='Playwright Selector' spellCheck='false' value={locator} disabled={mode !== 'none'} onChange={event => {
|
||||
setLocator(asLocator(source.language, event.target.value));
|
||||
setLocator(event.target.value);
|
||||
window.dispatch({ event: 'selectorUpdated', params: { selector: event.target.value } });
|
||||
}} />
|
||||
<ToolbarButton icon='files' title='Copy' onClick={() => {
|
||||
|
@ -70,10 +70,10 @@ test('should pick element', async ({ backend, connectedBrowser }) => {
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
selector: 'internal:role=button[name=\"Submit\"]',
|
||||
selector: 'internal:role=button[name=\"Submit\"s]',
|
||||
locator: 'getByRole(\'button\', { name: \'Submit\' })',
|
||||
}, {
|
||||
selector: 'internal:role=button[name=\"Submit\"]',
|
||||
selector: 'internal:role=button[name=\"Submit\"s]',
|
||||
locator: 'getByRole(\'button\', { name: \'Submit\' })',
|
||||
},
|
||||
]);
|
||||
|
@ -16,20 +16,31 @@
|
||||
|
||||
import { contextTest as it, expect } from '../config/browserTest';
|
||||
import { asLocator } from '../../packages/playwright-core/lib/server/isomorphic/locatorGenerators';
|
||||
import { parseLocator } from '../../packages/playwright-core/lib/server/isomorphic/locatorParser';
|
||||
import type { Page, Frame, Locator } from 'playwright-core';
|
||||
|
||||
function generate(locator: Locator) {
|
||||
return generateForSelector((locator as any)._selector);
|
||||
}
|
||||
|
||||
function generateForSelector(selector: string) {
|
||||
const result: any = {};
|
||||
for (const lang of ['javascript', 'python', 'java', 'csharp'])
|
||||
result[lang] = asLocator(lang, (locator as any)._selector, false);
|
||||
for (const lang of ['javascript', 'python', 'java', 'csharp']) {
|
||||
const locatorString = asLocator(lang, selector, false);
|
||||
expect.soft(parseLocator(locatorString), lang + ' mismatch').toBe(selector);
|
||||
result[lang] = locatorString;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function generateForNode(pageOrFrame: Page | Frame, target: string): Promise<string> {
|
||||
const selector = await pageOrFrame.locator(target).evaluate(e => (window as any).playwright.selector(e));
|
||||
const result: any = {};
|
||||
for (const lang of ['javascript', 'python', 'java', 'csharp'])
|
||||
result[lang] = asLocator(lang, selector, false);
|
||||
for (const lang of ['javascript', 'python', 'java', 'csharp']) {
|
||||
const locatorString = asLocator(lang, selector, false);
|
||||
expect.soft(parseLocator(locatorString)).toBe(selector);
|
||||
result[lang] = locatorString;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -43,14 +54,14 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
|
||||
expect.soft(generate(page.getByTestId('He"llo'))).toEqual({
|
||||
javascript: 'getByTestId(\'He"llo\')',
|
||||
python: 'get_by_test_id("He\\\"llo")',
|
||||
java: 'getByTestId("He\\\"llo")',
|
||||
csharp: 'GetByTestId("He\\\"llo")'
|
||||
python: 'get_by_test_id("He\\"llo")',
|
||||
java: 'getByTestId("He\\"llo")',
|
||||
csharp: 'GetByTestId("He\\"llo")'
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByText('Hello', { exact: true }))).toEqual({
|
||||
csharp: 'GetByText("Hello", new() { Exact: true })',
|
||||
java: 'getByText("Hello", new Page.GetByTextOptions().setExact(exact))',
|
||||
java: 'getByText("Hello", new Page.GetByTextOptions().setExact(true))',
|
||||
javascript: 'getByText(\'Hello\', { exact: true })',
|
||||
python: 'get_by_text("Hello", exact=true)',
|
||||
});
|
||||
@ -75,7 +86,7 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
});
|
||||
expect.soft(generate(page.getByLabel('Last Name', { exact: true }))).toEqual({
|
||||
csharp: 'GetByLabel("Last Name", new() { Exact: true })',
|
||||
java: 'getByLabel("Last Name", new Page.GetByLabelOptions().setExact(exact))',
|
||||
java: 'getByLabel("Last Name", new Page.GetByLabelOptions().setExact(true))',
|
||||
javascript: 'getByLabel(\'Last Name\', { exact: true })',
|
||||
python: 'get_by_label("Last Name", exact=true)',
|
||||
});
|
||||
@ -83,7 +94,7 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
csharp: 'GetByLabel(new Regex("Last\\\\s+name", RegexOptions.IgnoreCase))',
|
||||
java: 'getByLabel(Pattern.compile("Last\\\\s+name", Pattern.CASE_INSENSITIVE))',
|
||||
javascript: 'getByLabel(/Last\\s+name/i)',
|
||||
python: 'get_by_label(re.compile(r"Last\\\\s+name", re.IGNORECASE))',
|
||||
python: 'get_by_label(re.compile(r"Last\\s+name", re.IGNORECASE))',
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByPlaceholder('hello'))).toEqual({
|
||||
@ -94,7 +105,7 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
});
|
||||
expect.soft(generate(page.getByPlaceholder('Hello', { exact: true }))).toEqual({
|
||||
csharp: 'GetByPlaceholder("Hello", new() { Exact: true })',
|
||||
java: 'getByPlaceholder("Hello", new Page.GetByPlaceholderOptions().setExact(exact))',
|
||||
java: 'getByPlaceholder("Hello", new Page.GetByPlaceholderOptions().setExact(true))',
|
||||
javascript: 'getByPlaceholder(\'Hello\', { exact: true })',
|
||||
python: 'get_by_placeholder("Hello", exact=true)',
|
||||
});
|
||||
@ -113,7 +124,7 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
});
|
||||
expect.soft(generate(page.getByAltText('Hello', { exact: true }))).toEqual({
|
||||
csharp: 'GetByAltText("Hello", new() { Exact: true })',
|
||||
java: 'getByAltText("Hello", new Page.GetByAltTextOptions().setExact(exact))',
|
||||
java: 'getByAltText("Hello", new Page.GetByAltTextOptions().setExact(true))',
|
||||
javascript: 'getByAltText(\'Hello\', { exact: true })',
|
||||
python: 'get_by_alt_text("Hello", exact=true)',
|
||||
});
|
||||
@ -132,7 +143,7 @@ it('reverse engineer locators', async ({ page }) => {
|
||||
});
|
||||
expect.soft(generate(page.getByTitle('Hello', { exact: true }))).toEqual({
|
||||
csharp: 'GetByTitle("Hello", new() { Exact: true })',
|
||||
java: 'getByTitle("Hello", new Page.GetByTitleOptions().setExact(exact))',
|
||||
java: 'getByTitle("Hello", new Page.GetByTitleOptions().setExact(true))',
|
||||
javascript: 'getByTitle(\'Hello\', { exact: true })',
|
||||
python: 'get_by_title("Hello", exact=true)',
|
||||
});
|
||||
@ -183,7 +194,69 @@ it('reverse engineer ignore-case locators', async ({ page }) => {
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('selector generator', () => {
|
||||
it('reverse engineer ordered locators', async ({ page }) => {
|
||||
expect.soft(generate(page.locator('div').nth(3).first().last())).toEqual({
|
||||
csharp: `Locator(\"div\").Nth(3).First.Last`,
|
||||
java: `locator(\"div\").nth(3).first().last()`,
|
||||
javascript: `locator('div').nth(3).first().last()`,
|
||||
python: `locator(\"div\").nth(3).first.last`,
|
||||
});
|
||||
});
|
||||
|
||||
it('reverse engineer locators with regex', async ({ page }) => {
|
||||
expect.soft(generate(page.getByText(/he\/\sl\nlo/))).toEqual({
|
||||
csharp: `GetByText(new Regex(\"he\\\\/\\\\sl\\\\nlo\"))`,
|
||||
java: `getByText(Pattern.compile(\"he\\\\/\\\\sl\\\\nlo\"))`,
|
||||
javascript: `getByText(/he\\/\\sl\\nlo/)`,
|
||||
python: `get_by_text(re.compile(r"he/\\sl\\nlo"))`,
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByPlaceholder(/he\/\sl\nlo/))).toEqual({
|
||||
csharp: `GetByPlaceholder(new Regex(\"he\\\\/\\\\sl\\\\nlo\"))`,
|
||||
java: `getByPlaceholder(Pattern.compile(\"he\\\\/\\\\sl\\\\nlo\"))`,
|
||||
javascript: `getByPlaceholder(/he\\/\\sl\\nlo/)`,
|
||||
python: `get_by_placeholder(re.compile(r"he/\\sl\\nlo"))`,
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByText(/hel"lo/))).toEqual({
|
||||
csharp: `GetByText(new Regex("hel\\"lo"))`,
|
||||
java: `getByText(Pattern.compile("hel\\"lo"))`,
|
||||
javascript: `getByText(/hel\"lo/)`,
|
||||
python: `get_by_text(re.compile(r"hel\\"lo"))`,
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByPlaceholder(/hel"lo/))).toEqual({
|
||||
csharp: `GetByPlaceholder(new Regex("hel\\"lo"))`,
|
||||
java: `getByPlaceholder(Pattern.compile("hel\\"lo"))`,
|
||||
javascript: `getByPlaceholder(/hel"lo/)`,
|
||||
python: `get_by_placeholder(re.compile(r"hel\\"lo"))`,
|
||||
});
|
||||
});
|
||||
|
||||
it('reverse engineer hasText', async ({ page }) => {
|
||||
expect.soft(generate(page.getByText('Hello').filter({ hasText: 'wo"rld\n' }))).toEqual({
|
||||
csharp: `GetByText("Hello").Filter(new() { HasTextString: "wo\\"rld\\n" })`,
|
||||
java: `getByText("Hello").filter(new Locator.LocatorOptions().setHasText("wo\\"rld\\n"))`,
|
||||
javascript: `getByText('Hello').filter({ hasText: 'wo"rld\\n' })`,
|
||||
python: `get_by_text("Hello").filter(has_text="wo\\"rld\\n")`,
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByText('Hello').filter({ hasText: /wo\/\srld\n/ }))).toEqual({
|
||||
csharp: `GetByText("Hello").Filter(new() { HasTextString: new Regex("wo\\\\/\\\\srld\\\\n") })`,
|
||||
java: `getByText("Hello").filter(new Locator.LocatorOptions().setHasText(Pattern.compile("wo\\\\/\\\\srld\\\\n")))`,
|
||||
javascript: `getByText('Hello').filter({ hasText: /wo\\/\\srld\\n/ })`,
|
||||
python: `get_by_text("Hello").filter(has_text=re.compile(r"wo/\\srld\\n"))`,
|
||||
});
|
||||
|
||||
expect.soft(generate(page.getByText('Hello').filter({ hasText: /wor"ld/ }))).toEqual({
|
||||
csharp: `GetByText("Hello").Filter(new() { HasTextString: new Regex("wor\\"ld") })`,
|
||||
java: `getByText("Hello").filter(new Locator.LocatorOptions().setHasText(Pattern.compile("wor\\"ld")))`,
|
||||
javascript: `getByText('Hello').filter({ hasText: /wor"ld/ })`,
|
||||
python: `get_by_text("Hello").filter(has_text=re.compile(r"wor\\"ld"))`,
|
||||
});
|
||||
});
|
||||
|
||||
it.describe(() => {
|
||||
it.skip(({ mode }) => mode !== 'default');
|
||||
|
||||
it.beforeEach(async ({ context }) => {
|
||||
|
@ -50,7 +50,7 @@ it.describe('selector generator', () => {
|
||||
|
||||
it('should generate text for <input type=button>', async ({ page }) => {
|
||||
await page.setContent(`<input type=button value="Click me">`);
|
||||
expect(await generate(page, 'input')).toBe('internal:role=button[name=\"Click me\"]');
|
||||
expect(await generate(page, 'input')).toBe('internal:role=button[name=\"Click me\"s]');
|
||||
});
|
||||
|
||||
it('should trim text', async ({ page }) => {
|
||||
@ -88,7 +88,7 @@ it.describe('selector generator', () => {
|
||||
|
||||
it('should prefer data-testid', async ({ page }) => {
|
||||
await page.setContent(`<div>Text</div><div>Text</div><div data-testid=a>Text</div><div>Text</div>`);
|
||||
expect(await generate(page, '[data-testid="a"]')).toBe('internal:attr=[data-testid=\"a\"]');
|
||||
expect(await generate(page, '[data-testid="a"]')).toBe('internal:attr=[data-testid=\"a\"s]');
|
||||
});
|
||||
|
||||
it('should handle first non-unique data-testid', async ({ page }) => {
|
||||
@ -99,7 +99,7 @@ it.describe('selector generator', () => {
|
||||
<div data-testid=a>
|
||||
Text
|
||||
</div>`);
|
||||
expect(await generate(page, 'div[mark="1"]')).toBe('internal:attr=[data-testid=\"a\"] >> nth=0');
|
||||
expect(await generate(page, 'div[mark="1"]')).toBe('internal:attr=[data-testid=\"a\"s] >> nth=0');
|
||||
});
|
||||
|
||||
it('should handle second non-unique data-testid', async ({ page }) => {
|
||||
@ -110,7 +110,7 @@ it.describe('selector generator', () => {
|
||||
<div data-testid=a mark=1>
|
||||
Text
|
||||
</div>`);
|
||||
expect(await generate(page, 'div[mark="1"]')).toBe(`internal:attr=[data-testid=\"a\"] >> nth=1`);
|
||||
expect(await generate(page, 'div[mark="1"]')).toBe(`internal:attr=[data-testid=\"a\"s] >> nth=1`);
|
||||
});
|
||||
|
||||
it('should use readable id', async ({ page }) => {
|
||||
@ -319,7 +319,7 @@ it.describe('selector generator', () => {
|
||||
|
||||
await page.setContent(`<button><span></span></button><button></button>`);
|
||||
await page.$eval('button', button => button.setAttribute('aria-label', `!#'!?:`));
|
||||
expect(await generate(page, 'button')).toBe(`internal:role=button[name="!#'!?:"]`);
|
||||
expect(await generate(page, 'button')).toBe(`internal:role=button[name="!#'!?:"s]`);
|
||||
expect(await page.$(`role=button[name="!#'!?:"]`)).toBeTruthy();
|
||||
|
||||
await page.setContent(`<div><span></span></div>`);
|
||||
@ -343,7 +343,7 @@ it.describe('selector generator', () => {
|
||||
|
||||
it('should accept valid aria-label for candidate consideration', async ({ page }) => {
|
||||
await page.setContent(`<button aria-label="ariaLabel" id="buttonId"></button>`);
|
||||
expect(await generate(page, 'button')).toBe('internal:role=button[name=\"ariaLabel\"]');
|
||||
expect(await generate(page, 'button')).toBe('internal:role=button[name=\"ariaLabel\"s]');
|
||||
});
|
||||
|
||||
it('should ignore empty role for candidate consideration', async ({ page }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user