diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 8878ecb59c..b810e2fa65 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 9bae0a62bd..fff3d078ff 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -216,19 +216,24 @@ function transform(template: string, params: TemplateParams, testIdAttributeName } export function locatorOrSelectorAsSelector(language: Language, locator: string, testIdAttributeName: string): string { + try { + return unsafeLocatorOrSelectorAsSelector(language, locator, testIdAttributeName); + } catch (e) { + return ''; + } +} + +export function unsafeLocatorOrSelectorAsSelector(language: Language, locator: string, testIdAttributeName: string): string { try { parseSelector(locator); return locator; } catch (e) { } - 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; - } 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 c3fb643665..b71cae12a5 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -300,3 +300,9 @@ test('should highlight aria template', async ({ backend, connectedBrowser }, tes const box2 = roundBox(await highlight.boundingBox()); expect(box1).toEqual(box2); }); + +test('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:'); +});