diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 251dce7e037bd..23c0e1f9d1472 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -25,6 +25,9 @@ import { Recorder } from './recorder'; import { EmptyRecorderApp } from './recorder/recorderApp'; import { asLocator, type Language } from '../utils'; import { parseYamlForAriaSnapshot } from './ariaSnapshot'; +import type { ParsedYaml } from '../utils/isomorphic/ariaSnapshot'; +import { parseYamlTemplate } from '../utils/isomorphic/ariaSnapshot'; +import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser'; const internalMetadata = serverSideCallMetadata(); @@ -144,9 +147,17 @@ export class DebugController extends SdkObject { } async highlight(params: { selector?: string, ariaTemplate?: string }) { + // Assert parameters validity. + if (params.selector) + unsafeLocatorOrSelectorAsSelector(this._sdkLanguage, params.selector, 'data-testid'); + let parsedYaml: ParsedYaml | undefined; + if (params.ariaTemplate) { + parsedYaml = parseYamlForAriaSnapshot(params.ariaTemplate); + parseYamlTemplate(parsedYaml); + } for (const recorder of await this._allRecorders()) { - if (params.ariaTemplate) - recorder.setHighlightedAriaTemplate(parseYamlForAriaSnapshot(params.ariaTemplate)); + if (parsedYaml) + recorder.setHighlightedAriaTemplate(parsedYaml); else if (params.selector) recorder.setHighlightedSelector(this._sdkLanguage, params.selector); } diff --git a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts index e0c10b53fd20b..9caa7b4b8a88e 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -215,18 +215,23 @@ function transform(template: string, params: TemplateParams, testIdAttributeName export function locatorOrSelectorAsSelector(language: Language, locator: string, testIdAttributeName: string): string { try { - parseSelector(locator); - return locator; + return unsafeLocatorOrSelectorAsSelector(language, locator, testIdAttributeName); } catch (e) { + return ''; } +} + +export function unsafeLocatorOrSelectorAsSelector(language: Language, locator: string, testIdAttributeName: string): string { try { - const { selector, preferredQuote } = parseLocator(locator, testIdAttributeName); - const locators = asLocators(language, selector, undefined, undefined, preferredQuote); - const digest = digestForComparison(language, locator); - if (locators.some(candidate => digestForComparison(language, candidate) === digest)) - return selector; + parseSelector(locator); + return locator; } catch (e) { } + const { selector, preferredQuote } = parseLocator(locator, testIdAttributeName); + const locators = asLocators(language, selector, undefined, undefined, preferredQuote); + const digest = digestForComparison(language, locator); + if (locators.some(candidate => digestForComparison(language, candidate) === digest)) + return selector; return ''; } diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index 6050d4e64525d..67640d0963b1b 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -298,3 +298,9 @@ test('should highlight aria template', async ({ backend, connectedBrowser }, tes const box2 = roundBox(await highlight.boundingBox()); expect(box1).toEqual(box2); }); + +test.only('should report error in aria template', async ({ backend }) => { + await backend.navigate({ url: `data:text/html,` }); + const error = await backend.highlight({ ariaTemplate: `- button "Submit` }).catch(e => e); + expect(error.message).toContain('Unterminated string:'); +});