diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 46f8f610e0f87..b48b3fce7ed3b 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -2106,15 +2106,14 @@ Expected options currently selected. ## async method: LocatorAssertions.toMatchAriaSnapshot * since: v1.49 -* langs: js +* langs: + - alias-java: matchesAriaSnapshot Asserts that the target element matches the given accessibility snapshot. **Usage** ```js -import { role as x } from '@playwright/test'; -// ... await page.goto('https://demo.playwright.dev/todomvc/'); await expect(page.locator('body')).toMatchAriaSnapshot(` - heading "todos" @@ -2122,11 +2121,41 @@ await expect(page.locator('body')).toMatchAriaSnapshot(` `); ``` +```python async +await page.goto('https://demo.playwright.dev/todomvc/') +await expect(page.locator('body')).to_match_aria_snapshot(''' + - heading "todos" + - textbox "What needs to be done?" +''') +``` + +```python sync +page.goto('https://demo.playwright.dev/todomvc/') +expect(page.locator('body')).to_match_aria_snapshot(''' + - heading "todos" + - textbox "What needs to be done?" +''') +``` + +```csharp +await page.GotoAsync("https://demo.playwright.dev/todomvc/"); +await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@" + - heading ""todos"" + - textbox ""What needs to be done?"" +"); +``` + +```java +page.navigate("https://demo.playwright.dev/todomvc/"); +assertThat(page.locator("body")).matchesAriaSnapshot(""" + - heading "todos" + - textbox "What needs to be done?" +"""); +``` + ### param: LocatorAssertions.toMatchAriaSnapshot.expected * since: v1.49 -* langs: js - `expected` ### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%% * since: v1.49 -* langs: js diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index a5d4ca7d0b7dc..23e3cff257ceb 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -48,6 +48,7 @@ This project incorporates components from the projects listed below. The origina - stack-utils@2.0.5 (https://github.com/tapjs/stack-utils) - wrappy@1.0.2 (https://github.com/npm/wrappy) - ws@8.17.1 (https://github.com/websockets/ws) +- yaml@2.6.0 (https://github.com/eemeli/yaml) - yauzl@2.10.0 (https://github.com/thejoshwolfe/yauzl) - yazl@2.5.1 (https://github.com/thejoshwolfe/yazl) @@ -1121,6 +1122,24 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF ws@8.17.1 AND INFORMATION +%% yaml@2.6.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright Eemeli Aro + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +========================================= +END OF yaml@2.6.0 AND INFORMATION + %% yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -1175,6 +1194,6 @@ END OF yazl@2.5.1 AND INFORMATION SUMMARY BEGIN HERE ========================================= -Total Packages: 46 +Total Packages: 47 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/playwright-core/bundles/utils/package-lock.json b/packages/playwright-core/bundles/utils/package-lock.json index 0e5e761433bf0..f786cb4db7bd5 100644 --- a/packages/playwright-core/bundles/utils/package-lock.json +++ b/packages/playwright-core/bundles/utils/package-lock.json @@ -25,7 +25,8 @@ "signal-exit": "3.0.7", "socks-proxy-agent": "8.0.4", "stack-utils": "2.0.5", - "ws": "8.17.1" + "ws": "8.17.1", + "yaml": "^2.5.1" }, "devDependencies": { "@types/debug": "^4.1.7", @@ -432,6 +433,17 @@ "optional": true } } + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } } }, "dependencies": { @@ -726,6 +738,11 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "requires": {} + }, + "yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==" } } } diff --git a/packages/playwright-core/bundles/utils/package.json b/packages/playwright-core/bundles/utils/package.json index 06637adabee9a..005e32fbe6a62 100644 --- a/packages/playwright-core/bundles/utils/package.json +++ b/packages/playwright-core/bundles/utils/package.json @@ -26,6 +26,7 @@ "signal-exit": "3.0.7", "socks-proxy-agent": "8.0.4", "stack-utils": "2.0.5", + "yaml": "^2.5.1", "ws": "8.17.1" }, "devDependencies": { diff --git a/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts b/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts index dcb37906290f0..975e291bb5dbc 100644 --- a/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts +++ b/packages/playwright-core/bundles/utils/src/utilsBundleImpl.ts @@ -54,6 +54,9 @@ export { SocksProxyAgent } from 'socks-proxy-agent'; import StackUtilsLibrary from 'stack-utils'; export const StackUtils = StackUtilsLibrary; +import yamlLibrary from 'yaml'; +export const yaml = yamlLibrary; + // @ts-ignore import wsLibrary, { WebSocketServer, Receiver, Sender } from 'ws'; export const ws = wsLibrary; diff --git a/packages/playwright-core/src/server/ariaSnapshot.ts b/packages/playwright-core/src/server/ariaSnapshot.ts new file mode 100644 index 0000000000000..6f89dd21cfc80 --- /dev/null +++ b/packages/playwright-core/src/server/ariaSnapshot.ts @@ -0,0 +1,74 @@ +/** + * 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 type { AriaTemplateNode } from './injected/ariaSnapshot'; +import { yaml } from '../utilsBundle'; + +export function parseAriaSnapshot(text: string): AriaTemplateNode { + type YamlNode = Record | string>; + + const parseKey = (key: string): AriaTemplateNode => { + if (!key) + return { role: '' }; + + const match = key.match(/^([a-z]+)(?:\s+(?:"([^"]*)"|\/([^\/]*)\/))?$/); + + if (!match) + throw new Error(`Invalid key ${key}`); + + const role = match[1]; + if (role && role !== 'text' && !allRoles.includes(role)) + throw new Error(`Invalid role ${role}`); + + if (match[2]) + return { role, name: match[2] }; + if (match[3]) + return { role, name: new RegExp(match[3]) }; + return { role }; + }; + + const valueOrRegex = (value: string): string | RegExp => { + return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value; + }; + + const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => { + const key = typeof object === 'string' ? object : Object.keys(object)[0]; + const value = typeof object === 'string' ? undefined : object[key]; + const parsed = parseKey(key); + if (parsed.role === 'text') { + if (typeof value !== 'string') + throw new Error(`Generic role must have a text value`); + return valueOrRegex(value as string); + } + if (Array.isArray(value)) + parsed.children = value.map(convert); + else if (value) + parsed.children = [valueOrRegex(value)]; + return parsed; + }; + const fragment = yaml.parse(text) as YamlNode[]; + return convert({ '': fragment }) as AriaTemplateNode; +} + +const allRoles = [ + 'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox', 'command', + 'complementary', 'composite', 'contentinfo', 'definition', 'deletion', 'dialog', 'directory', 'document', 'emphasis', 'feed', 'figure', 'form', 'generic', 'grid', + 'gridcell', 'group', 'heading', 'img', 'input', 'insertion', 'landmark', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'meter', 'menu', + 'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'paragraph', 'presentation', 'progressbar', 'radio', 'radiogroup', + 'range', 'region', 'roletype', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'section', 'sectionhead', 'select', 'separator', 'slider', + 'spinbutton', 'status', 'strong', 'structure', 'subscript', 'superscript', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'time', 'timer', + 'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem', 'widget', 'window' +]; diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 6dcb9a7220fcd..d058085cb71b2 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -26,6 +26,7 @@ import type { CallMetadata } from '../instrumentation'; import type { BrowserContextDispatcher } from './browserContextDispatcher'; import type { PageDispatcher } from './pageDispatcher'; import { debugAssert } from '../../utils'; +import { parseAriaSnapshot } from '../ariaSnapshot'; export class FrameDispatcher extends Dispatcher implements channels.FrameChannel { _type_Frame = true; @@ -258,7 +259,9 @@ export class FrameDispatcher extends Dispatcher { metadata.potentiallyClosesScope = true; - const expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined; + let expectedValue = params.expectedValue ? parseArgument(params.expectedValue) : undefined; + if (params.expression === 'to.match.aria' && expectedValue) + expectedValue = parseAriaSnapshot(expectedValue); const result = await this._frame.expect(metadata, params.selector, { ...params, expectedValue }); if (result.received !== undefined) result.received = serializeResult(result.received); diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index d620ca273cd1a..fd2cdcdb7cdae 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -704,7 +704,7 @@ class TextAssertionTool implements RecorderTool { value: (target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).value, }; } - } if (this._kind === 'snapshot') { + } else if (this._kind === 'snapshot') { this._hoverHighlight = this._recorder.injectedScript.generateSelector(target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); this._hoverHighlight.color = '#8acae480'; // forTextExpect can update the target, re-highlight it. diff --git a/packages/playwright-core/src/utilsBundle.ts b/packages/playwright-core/src/utilsBundle.ts index a2a62be867e6d..b330491e0cbed 100644 --- a/packages/playwright-core/src/utilsBundle.ts +++ b/packages/playwright-core/src/utilsBundle.ts @@ -31,6 +31,7 @@ export const PNG: typeof import('../bundles/utils/node_modules/@types/pngjs').PN export const program: typeof import('../bundles/utils/node_modules/commander').program = require('./utilsBundleImpl').program; export const progress: typeof import('../bundles/utils/node_modules/@types/progress') = require('./utilsBundleImpl').progress; export const SocksProxyAgent: typeof import('../bundles/utils/node_modules/socks-proxy-agent').SocksProxyAgent = require('./utilsBundleImpl').SocksProxyAgent; +export const yaml: typeof import('../bundles/utils/node_modules/yaml') = require('./utilsBundleImpl').yaml; export const ws: typeof import('../bundles/utils/node_modules/@types/ws') = require('./utilsBundleImpl').ws; export const wsServer: typeof import('../bundles/utils/node_modules/@types/ws').WebSocketServer = require('./utilsBundleImpl').wsServer; export const wsReceiver = require('./utilsBundleImpl').wsReceiver; diff --git a/packages/playwright/ThirdPartyNotices.txt b/packages/playwright/ThirdPartyNotices.txt index 46c2f60cd2ffe..f2bb64d661c29 100644 --- a/packages/playwright/ThirdPartyNotices.txt +++ b/packages/playwright/ThirdPartyNotices.txt @@ -155,7 +155,6 @@ This project incorporates components from the projects listed below. The origina - undici-types@6.19.8 (https://github.com/nodejs/undici) - update-browserslist-db@1.0.13 (https://github.com/browserslist/update-db) - yallist@3.1.1 (https://github.com/isaacs/yallist) -- yaml@2.5.1 (https://github.com/eemeli/yaml) %% @ampproject/remapping@2.2.1 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -4398,26 +4397,8 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF yallist@3.1.1 AND INFORMATION -%% yaml@2.5.1 NOTICES AND INFORMATION BEGIN HERE -========================================= -Copyright Eemeli Aro - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS -OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -THIS SOFTWARE. -========================================= -END OF yaml@2.5.1 AND INFORMATION - SUMMARY BEGIN HERE ========================================= -Total Packages: 152 +Total Packages: 151 ========================================= END OF SUMMARY \ No newline at end of file diff --git a/packages/playwright/bundles/utils/package-lock.json b/packages/playwright/bundles/utils/package-lock.json index 90df9a258bd57..fcf9f972feb31 100644 --- a/packages/playwright/bundles/utils/package-lock.json +++ b/packages/playwright/bundles/utils/package-lock.json @@ -13,8 +13,7 @@ "json5": "2.2.3", "pirates": "4.0.4", "source-map-support": "0.5.21", - "stoppable": "1.1.0", - "yaml": "^2.5.1" + "stoppable": "1.1.0" }, "devDependencies": { "@types/source-map-support": "^0.5.4", @@ -281,17 +280,6 @@ "engines": { "node": ">=8.0" } - }, - "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } } }, "dependencies": { @@ -476,11 +464,6 @@ "requires": { "is-number": "^7.0.0" } - }, - "yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==" } } } diff --git a/packages/playwright/bundles/utils/package.json b/packages/playwright/bundles/utils/package.json index dc807c0d95202..69477909c5337 100644 --- a/packages/playwright/bundles/utils/package.json +++ b/packages/playwright/bundles/utils/package.json @@ -14,8 +14,7 @@ "json5": "2.2.3", "pirates": "4.0.4", "source-map-support": "0.5.21", - "stoppable": "1.1.0", - "yaml": "^2.5.1" + "stoppable": "1.1.0" }, "devDependencies": { "@types/source-map-support": "^0.5.4", diff --git a/packages/playwright/bundles/utils/src/utilsBundleImpl.ts b/packages/playwright/bundles/utils/src/utilsBundleImpl.ts index 76cf961ab7c55..7c29c301a8924 100644 --- a/packages/playwright/bundles/utils/src/utilsBundleImpl.ts +++ b/packages/playwright/bundles/utils/src/utilsBundleImpl.ts @@ -31,6 +31,3 @@ export const enquirer = enquirerLibrary; import chokidarLibrary from 'chokidar'; export const chokidar = chokidarLibrary; - -import yamlLibrary from 'yaml'; -export const yaml = yamlLibrary; diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 949e44af6fd3d..cd79ccab61718 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -18,8 +18,6 @@ import type { LocatorEx } from './matchers'; import type { ExpectMatcherState } from '../../types/test'; import { kNoElementsFoundError, matcherHint, type MatcherResult } from './matcherHint'; -import type { AriaTemplateNode } from 'playwright-core/lib/server/injected/ariaSnapshot'; -import { yaml } from '../utilsBundle'; import { colors } from 'playwright-core/lib/utilsBundle'; import { EXPECTED_COLOR } from '../common/expectBundle'; import { callLogText } from '../util'; @@ -46,9 +44,8 @@ export async function toMatchAriaSnapshot( ].join('\n\n')); } - const ariaTree = toAriaTree(expected) as AriaTemplateNode; const timeout = options.timeout ?? this.timeout; - const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: ariaTree, isNot: this.isNot, timeout }); + const { matches: pass, received, log, timedOut } = await receiver._expect('to.match.aria', { expectedValue: expected, isNot: this.isNot, timeout }); const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined); const notFound = received === kNoElementsFoundError; @@ -76,59 +73,3 @@ export async function toMatchAriaSnapshot( timeout: timedOut ? timeout : undefined, }; } - -function parseKey(key: string): AriaTemplateNode { - if (!key) - return { role: '' }; - - const match = key.match(/^([a-z]+)(?:\s+(?:"([^"]*)"|\/([^\/]*)\/))?$/); - - if (!match) - throw new Error(`Invalid key ${key}`); - - const role = match[1]; - if (role && role !== 'text' && !allRoles.includes(role)) - throw new Error(`Invalid role ${role}`); - - if (match[2]) - return { role, name: match[2] }; - if (match[3]) - return { role, name: new RegExp(match[3]) }; - return { role }; -} - -function valueOrRegex(value: string): string | RegExp { - return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value; -} - -type YamlNode = Record | string>; - -function toAriaTree(text: string): AriaTemplateNode { - const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => { - const key = typeof object === 'string' ? object : Object.keys(object)[0]; - const value = typeof object === 'string' ? undefined : object[key]; - const parsed = parseKey(key); - if (parsed.role === 'text') { - if (typeof value !== 'string') - throw new Error(`Generic role must have a text value`); - return valueOrRegex(value as string); - } - if (Array.isArray(value)) - parsed.children = value.map(convert); - else if (value) - parsed.children = [valueOrRegex(value)]; - return parsed; - }; - const fragment = yaml.parse(text) as YamlNode[]; - return convert({ '': fragment }) as AriaTemplateNode; -} - -const allRoles = [ - 'alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox', 'command', - 'complementary', 'composite', 'contentinfo', 'definition', 'deletion', 'dialog', 'directory', 'document', 'emphasis', 'feed', 'figure', 'form', 'generic', 'grid', - 'gridcell', 'group', 'heading', 'img', 'input', 'insertion', 'landmark', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'meter', 'menu', - 'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'paragraph', 'presentation', 'progressbar', 'radio', 'radiogroup', - 'range', 'region', 'roletype', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'section', 'sectionhead', 'select', 'separator', 'slider', - 'spinbutton', 'status', 'strong', 'structure', 'subscript', 'superscript', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'time', 'timer', - 'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem', 'widget', 'window' -]; diff --git a/packages/playwright/src/utilsBundle.ts b/packages/playwright/src/utilsBundle.ts index 5ded7993b7f70..072e16bb037d6 100644 --- a/packages/playwright/src/utilsBundle.ts +++ b/packages/playwright/src/utilsBundle.ts @@ -20,4 +20,3 @@ export const sourceMapSupport: typeof import('../bundles/utils/node_modules/@typ export const stoppable: typeof import('../bundles/utils/node_modules/@types/stoppable') = require('./utilsBundleImpl').stoppable; export const enquirer: typeof import('../bundles/utils/node_modules/enquirer') = require('./utilsBundleImpl').enquirer; export const chokidar: typeof import('../bundles/utils/node_modules/chokidar') = require('./utilsBundleImpl').chokidar; -export const yaml: typeof import('../bundles/utils/node_modules/yaml') = require('./utilsBundleImpl').yaml; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 3f1179e34fa03..c96de2091a3f1 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -7644,8 +7644,6 @@ interface LocatorAssertions { * **Usage** * * ```js - * import { role as x } from '@playwright/test'; - * // ... * await page.goto('https://demo.playwright.dev/todomvc/'); * await expect(page.locator('body')).toMatchAriaSnapshot(` * - heading "todos" diff --git a/tests/library/inspector/cli-codegen-aria.spec.ts b/tests/library/inspector/cli-codegen-aria.spec.ts new file mode 100644 index 0000000000000..f99f65fd6f212 --- /dev/null +++ b/tests/library/inspector/cli-codegen-aria.spec.ts @@ -0,0 +1,42 @@ +/** + * 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 { test, expect } from './inspectorTest'; + +test.describe(() => { + test.skip(({ mode }) => mode !== 'default'); + test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events'); + + test('should generate aria snapshot', async ({ openRecorder }) => { + const { recorder } = await openRecorder(); + await recorder.setContentAndWait(`
`); + + await recorder.page.click('x-pw-tool-item.snapshot'); + await recorder.page.hover('button'); + await recorder.trustedClick(); + + await expect.poll(() => + recorder.text('JavaScript')).toContain(`await expect(page.getByRole('button')).toMatchAriaSnapshot(\`- button "Submit"\`);`); + await expect.poll(() => + recorder.text('Python')).toContain(`expect(page.get_by_role("button")).to_match_aria_snapshot("- button \\"Submit\\"")`); + await expect.poll(() => + recorder.text('Python Async')).toContain(`await expect(page.get_by_role(\"button\")).to_match_aria_snapshot("- button \\"Submit\\"")`); + await expect.poll(() => + recorder.text('Java')).toContain(`assertThat(page.getByRole(AriaRole.BUTTON)).matchesAriaSnapshot("- button \\"Submit\\"");`); + await expect.poll(() => + recorder.text('C#')).toContain(`await Expect(page.GetByRole(AriaRole.Button)).ToMatchAriaSnapshotAsync("- button \\"Submit\\"");`); + }); +}); diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index 524fab1e57a01..3ed700a20819a 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -160,6 +160,15 @@ export class Recorder { return this._sources; } + async text(file: string): Promise { + const sources: Source[] = await this.recorderPage.evaluate(() => (window as any).playwrightSourcesEchoForTest || []); + for (const source of sources) { + if (codegenLangId2lang.get(source.id) === file) + return source.text; + } + return ''; + } + async waitForHighlight(action: () => Promise): Promise { await this.page.$$eval('x-pw-highlight', els => els.forEach(e => e.remove())); await this.page.$$eval('x-pw-tooltip', els => els.forEach(e => e.remove()));