diff --git a/packages/playwright-core/src/server/ariaSnapshot.ts b/packages/playwright-core/src/server/ariaSnapshot.ts index 6f89dd21cf..e450e5b15d 100644 --- a/packages/playwright-core/src/server/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/ariaSnapshot.ts @@ -40,8 +40,12 @@ export function parseAriaSnapshot(text: string): AriaTemplateNode { return { role }; }; + const normalizeWhitespace = (text: string) => { + return text.replace(/[\r\n\s\t]+/g, ' ').trim(); + }; + const valueOrRegex = (value: string): string | RegExp => { - return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value; + return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : normalizeWhitespace(value); }; const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => { diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts index 22ec9b5c42..907006ce0a 100644 --- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts @@ -15,13 +15,13 @@ */ import { escapeWithQuotes } from '@isomorphic/stringUtils'; -import { beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, isElementIgnoredForAria } from './roleUtils'; -import { isElementVisible, isElementStyleVisibilityVisible } from './domUtils'; +import { accumulatedElementText, beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, getPseudoContent, isElementIgnoredForAria } from './roleUtils'; +import { isElementVisible, isElementStyleVisibilityVisible, getElementComputedStyle } from './domUtils'; type AriaNode = { role: string; name?: string; - children?: (AriaNode | string)[]; + children: (AriaNode | string)[]; }; export type AriaTemplateNode = { @@ -38,16 +38,20 @@ export function generateAriaTree(rootElement: Element): AriaNode { const name = role ? getElementAccessibleName(element, false) || undefined : undefined; const isLeaf = leafRoles.has(role); - const result: AriaNode = { role, name }; - if (isLeaf && !name && element.textContent) - result.children = [element.textContent]; + const result: AriaNode = { role, name, children: [] }; + if (isLeaf && !name) { + const text = accumulatedElementText(element); + if (text) + result.children = [text]; + } return { isLeaf, ariaNode: result }; }; const visit = (ariaNode: AriaNode, node: Node) => { if (node.nodeType === Node.TEXT_NODE && node.nodeValue) { - ariaNode.children = ariaNode.children || []; - ariaNode.children.push(node.nodeValue); + const text = node.nodeValue; + if (text) + ariaNode.children.push(node.nodeValue || ''); return; } @@ -67,10 +71,8 @@ export function generateAriaTree(rootElement: Element): AriaNode { if (visible) { const childAriaNode = toAriaNode(element); const isHiddenContainer = childAriaNode && hiddenContainerRoles.has(childAriaNode.ariaNode.role); - if (childAriaNode && !isHiddenContainer) { - ariaNode.children = ariaNode.children || []; + if (childAriaNode && !isHiddenContainer) ariaNode.children.push(childAriaNode.ariaNode); - } if (isHiddenContainer || !childAriaNode?.isLeaf) processChildNodes(childAriaNode?.ariaNode || ariaNode, element); } else { @@ -79,18 +81,36 @@ export function generateAriaTree(rootElement: Element): AriaNode { }; function processChildNodes(ariaNode: AriaNode, element: Element) { - // Process light DOM children - for (let child = element.firstChild; child; child = child.nextSibling) - visit(ariaNode, child); - // Process shadow DOM children, if any - if (element.shadowRoot) { - for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling) + // Surround every element with spaces for the sake of concatenated text nodes. + const display = getElementComputedStyle(element)?.display || 'inline'; + const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : ''; + if (treatAsBlock) + ariaNode.children.push(treatAsBlock); + + ariaNode.children.push(getPseudoContent(element, '::before')); + const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : []; + if (assignedNodes.length) { + for (const child of assignedNodes) visit(ariaNode, child); + } else { + for (let child = element.firstChild; child; child = child.nextSibling) { + if (!(child as Element | Text).assignedSlot) + visit(ariaNode, child); + } + if (element.shadowRoot) { + for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling) + visit(ariaNode, child); + } } + + ariaNode.children.push(getPseudoContent(element, '::after')); + + if (treatAsBlock) + ariaNode.children.push(treatAsBlock); } beginAriaCaches(); - const ariaRoot: AriaNode = { role: '' }; + const ariaRoot: AriaNode = { role: '', children: [] }; try { visit(ariaRoot, rootElement); } finally { @@ -128,7 +148,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) { } } flushChildren(buffer, normalizedChildren); - ariaNode.children = normalizedChildren.length ? normalizedChildren : undefined; + ariaNode.children = normalizedChildren.length ? normalizedChildren : []; }; visit(rootA11yNode); } @@ -144,7 +164,7 @@ const leafRoles = new Set([ 'textbox', 'time', 'tooltip' ]); -const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\n]+/g, ' '); +const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' '); function matchesText(text: string | undefined, template: RegExp | string | undefined) { if (!template) @@ -233,7 +253,7 @@ export function renderAriaTree(ariaNode: AriaNode): string { lines.push(line); return; } - lines.push(line + (ariaNode.children ? ':' : '')); + lines.push(line + (ariaNode.children.length ? ':' : '')); for (const child of ariaNode.children || []) visit(child, indent + ' '); }; diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts index 6e05c39901..d085a8e36d 100644 --- a/packages/playwright-core/src/server/injected/roleUtils.ts +++ b/packages/playwright-core/src/server/injected/roleUtils.ts @@ -363,7 +363,7 @@ function queryInAriaOwned(element: Element, selector: string): Element[] { return result; } -function getPseudoContent(element: Element, pseudo: '::before' | '::after') { +export function getPseudoContent(element: Element, pseudo: '::before' | '::after') { const cache = pseudo === '::before' ? cachePseudoContentBefore : cachePseudoContentAfter; if (cache?.has(element)) return cache?.get(element) || ''; @@ -430,10 +430,6 @@ export function getElementAccessibleName(element: Element, includeHidden: boolea accessibleName = asFlatString(getTextAlternativeInternal(element, { includeHidden, visitedElements: new Set(), - embeddedInDescribedBy: undefined, - embeddedInLabelledBy: undefined, - embeddedInLabel: undefined, - embeddedInNativeTextAlternative: undefined, embeddedInTargetElement: 'self', })); } @@ -458,10 +454,6 @@ export function getElementAccessibleDescription(element: Element, includeHidden: accessibleDescription = asFlatString(describedBy.map(ref => getTextAlternativeInternal(ref, { includeHidden, visitedElements: new Set(), - embeddedInLabelledBy: undefined, - embeddedInLabel: undefined, - embeddedInNativeTextAlternative: undefined, - embeddedInTargetElement: 'none', embeddedInDescribedBy: { element: ref, hidden: isElementHiddenForAria(ref) }, })).join(' ')); } else if (element.hasAttribute('aria-description')) { @@ -480,13 +472,13 @@ export function getElementAccessibleDescription(element: Element, includeHidden: } type AccessibleNameOptions = { - includeHidden: boolean, visitedElements: Set, - embeddedInDescribedBy: { element: Element, hidden: boolean } | undefined, - embeddedInLabelledBy: { element: Element, hidden: boolean } | undefined, - embeddedInLabel: { element: Element, hidden: boolean } | undefined, - embeddedInNativeTextAlternative: { element: Element, hidden: boolean } | undefined, - embeddedInTargetElement: 'none' | 'self' | 'descendant', + includeHidden?: boolean, + embeddedInDescribedBy?: { element: Element, hidden: boolean }, + embeddedInLabelledBy?: { element: Element, hidden: boolean }, + embeddedInLabel?: { element: Element, hidden: boolean }, + embeddedInNativeTextAlternative?: { element: Element, hidden: boolean }, + embeddedInTargetElement?: 'self' | 'descendant', }; function getTextAlternativeInternal(element: Element, options: AccessibleNameOptions): string { @@ -525,7 +517,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt ...options, embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) }, embeddedInDescribedBy: undefined, - embeddedInTargetElement: 'none', + embeddedInTargetElement: undefined, embeddedInLabel: undefined, embeddedInNativeTextAlternative: undefined, })).join(' '); @@ -778,42 +770,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt !!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy || !!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) { options.visitedElements.add(element); - const tokens: string[] = []; - const visit = (node: Node, skipSlotted: boolean) => { - if (skipSlotted && (node as Element | Text).assignedSlot) - return; - if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { - const display = getElementComputedStyle(node as Element)?.display || 'inline'; - let token = getTextAlternativeInternal(node as Element, childOptions); - // SPEC DIFFERENCE. - // Spec says "append the result to the accumulated text", assuming "with space". - // However, multiple tests insist that inline elements do not add a space. - // Additionally,
insists on a space anyway, see "name_file-label-inline-block-elements-manual.html" - if (display !== 'inline' || node.nodeName === 'BR') - token = ' ' + token + ' '; - tokens.push(token); - } else if (node.nodeType === 3 /* Node.TEXT_NODE */) { - // step 2g. - tokens.push(node.textContent || ''); - } - }; - tokens.push(getPseudoContent(element, '::before')); - const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : []; - if (assignedNodes.length) { - for (const child of assignedNodes) - visit(child, false); - } else { - for (let child = element.firstChild; child; child = child.nextSibling) - visit(child, true); - if (element.shadowRoot) { - for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling) - visit(child, true); - } - for (const owned of getIdRefs(element, element.getAttribute('aria-owns'))) - visit(owned, true); - } - tokens.push(getPseudoContent(element, '::after')); - const accessibleName = tokens.join(''); + const accessibleName = innerAccumulatedElementText(element, childOptions); // Spec says "Return the accumulated text if it is not the empty string". However, that is not really // compatible with the real browser behavior and wpt tests, where an element with empty contents will fallback to the title. // So we follow the spec everywhere except for the target element itself. This can probably be improved. @@ -834,6 +791,50 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt return ''; } +function innerAccumulatedElementText(element: Element, options: AccessibleNameOptions): string { + const tokens: string[] = []; + const visit = (node: Node, skipSlotted: boolean) => { + if (skipSlotted && (node as Element | Text).assignedSlot) + return; + if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { + const display = getElementComputedStyle(node as Element)?.display || 'inline'; + let token = getTextAlternativeInternal(node as Element, options); + // SPEC DIFFERENCE. + // Spec says "append the result to the accumulated text", assuming "with space". + // However, multiple tests insist that inline elements do not add a space. + // Additionally,
insists on a space anyway, see "name_file-label-inline-block-elements-manual.html" + if (display !== 'inline' || node.nodeName === 'BR') + token = ' ' + token + ' '; + tokens.push(token); + } else if (node.nodeType === 3 /* Node.TEXT_NODE */) { + // step 2g. + tokens.push(node.textContent || ''); + } + }; + tokens.push(getPseudoContent(element, '::before')); + const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : []; + if (assignedNodes.length) { + for (const child of assignedNodes) + visit(child, false); + } else { + for (let child = element.firstChild; child; child = child.nextSibling) + visit(child, true); + if (element.shadowRoot) { + for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling) + visit(child, true); + } + for (const owned of getIdRefs(element, element.getAttribute('aria-owns'))) + visit(owned, true); + } + tokens.push(getPseudoContent(element, '::after')); + return tokens.join(''); +} + +export function accumulatedElementText(element: Element): string { + const visitedElements = new Set(); + return asFlatString(innerAccumulatedElementText(element, { visitedElements })).trim(); +} + export const kAriaSelectedRoles = ['gridcell', 'option', 'row', 'tab', 'rowheader', 'columnheader', 'treeitem']; export function getAriaSelected(element: Element): boolean { // https://www.w3.org/TR/wai-aria-1.2/#aria-selected @@ -958,7 +959,7 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable !!accessibleName).join(' '); } diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index cd79ccab61..cf043c2ca8 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -49,17 +49,19 @@ export async function toMatchAriaSnapshot( const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined); const notFound = received === kNoElementsFoundError; + const escapedExpected = escapePrivateUsePoints(expected); + const escapedReceived = escapePrivateUsePoints(received); const message = () => { if (pass) { if (notFound) - return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log); - const printedReceived = printReceivedStringContainExpectedSubstring(received, received.indexOf(expected), expected.length); - return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived string: ${printedReceived}` + callLogText(log); + return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log); + const printedReceived = printReceivedStringContainExpectedSubstring(escapedReceived, escapedReceived.indexOf(escapedExpected), escapedExpected.length); + return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived string: ${printedReceived}` + callLogText(log); } else { const labelExpected = `Expected`; if (notFound) - return messagePrefix + `${labelExpected}: ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log); - return messagePrefix + this.utils.printDiffOrStringify(expected, received, labelExpected, 'Received string', false) + callLogText(log); + return messagePrefix + `${labelExpected}: ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log); + return messagePrefix + this.utils.printDiffOrStringify(escapedExpected, escapedReceived, labelExpected, 'Received string', false) + callLogText(log); } }; @@ -73,3 +75,7 @@ export async function toMatchAriaSnapshot( timeout: timedOut ? timeout : undefined, }; } + +function escapePrivateUsePoints(str: string) { + return str.replace(/[\uE000-\uF8FF]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`); +} diff --git a/tests/assets/codicon.css b/tests/assets/codicon.css new file mode 100644 index 0000000000..41360ce21d --- /dev/null +++ b/tests/assets/codicon.css @@ -0,0 +1,596 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@font-face { + font-family: "codicon"; + src: url("codicon.ttf") format("truetype"); +} + +.codicon { + font: normal normal normal 16px/1 codicon; + flex: none; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.codicon-add:before { content: '\ea60'; } +.codicon-plus:before { content: '\ea60'; } +.codicon-gist-new:before { content: '\ea60'; } +.codicon-repo-create:before { content: '\ea60'; } +.codicon-lightbulb:before { content: '\ea61'; } +.codicon-light-bulb:before { content: '\ea61'; } +.codicon-repo:before { content: '\ea62'; } +.codicon-repo-delete:before { content: '\ea62'; } +.codicon-gist-fork:before { content: '\ea63'; } +.codicon-repo-forked:before { content: '\ea63'; } +.codicon-git-pull-request:before { content: '\ea64'; } +.codicon-git-pull-request-abandoned:before { content: '\ea64'; } +.codicon-record-keys:before { content: '\ea65'; } +.codicon-keyboard:before { content: '\ea65'; } +.codicon-tag:before { content: '\ea66'; } +.codicon-git-pull-request-label:before { content: '\ea66'; } +.codicon-tag-add:before { content: '\ea66'; } +.codicon-tag-remove:before { content: '\ea66'; } +.codicon-person:before { content: '\ea67'; } +.codicon-person-follow:before { content: '\ea67'; } +.codicon-person-outline:before { content: '\ea67'; } +.codicon-person-filled:before { content: '\ea67'; } +.codicon-git-branch:before { content: '\ea68'; } +.codicon-git-branch-create:before { content: '\ea68'; } +.codicon-git-branch-delete:before { content: '\ea68'; } +.codicon-source-control:before { content: '\ea68'; } +.codicon-mirror:before { content: '\ea69'; } +.codicon-mirror-public:before { content: '\ea69'; } +.codicon-star:before { content: '\ea6a'; } +.codicon-star-add:before { content: '\ea6a'; } +.codicon-star-delete:before { content: '\ea6a'; } +.codicon-star-empty:before { content: '\ea6a'; } +.codicon-comment:before { content: '\ea6b'; } +.codicon-comment-add:before { content: '\ea6b'; } +.codicon-alert:before { content: '\ea6c'; } +.codicon-warning:before { content: '\ea6c'; } +.codicon-search:before { content: '\ea6d'; } +.codicon-search-save:before { content: '\ea6d'; } +.codicon-log-out:before { content: '\ea6e'; } +.codicon-sign-out:before { content: '\ea6e'; } +.codicon-log-in:before { content: '\ea6f'; } +.codicon-sign-in:before { content: '\ea6f'; } +.codicon-eye:before { content: '\ea70'; } +.codicon-eye-unwatch:before { content: '\ea70'; } +.codicon-eye-watch:before { content: '\ea70'; } +.codicon-circle-filled:before { content: '\ea71'; } +.codicon-primitive-dot:before { content: '\ea71'; } +.codicon-close-dirty:before { content: '\ea71'; } +.codicon-debug-breakpoint:before { content: '\ea71'; } +.codicon-debug-breakpoint-disabled:before { content: '\ea71'; } +.codicon-debug-hint:before { content: '\ea71'; } +.codicon-terminal-decoration-success:before { content: '\ea71'; } +.codicon-primitive-square:before { content: '\ea72'; } +.codicon-edit:before { content: '\ea73'; } +.codicon-pencil:before { content: '\ea73'; } +.codicon-info:before { content: '\ea74'; } +.codicon-issue-opened:before { content: '\ea74'; } +.codicon-gist-private:before { content: '\ea75'; } +.codicon-git-fork-private:before { content: '\ea75'; } +.codicon-lock:before { content: '\ea75'; } +.codicon-mirror-private:before { content: '\ea75'; } +.codicon-close:before { content: '\ea76'; } +.codicon-remove-close:before { content: '\ea76'; } +.codicon-x:before { content: '\ea76'; } +.codicon-repo-sync:before { content: '\ea77'; } +.codicon-sync:before { content: '\ea77'; } +.codicon-clone:before { content: '\ea78'; } +.codicon-desktop-download:before { content: '\ea78'; } +.codicon-beaker:before { content: '\ea79'; } +.codicon-microscope:before { content: '\ea79'; } +.codicon-vm:before { content: '\ea7a'; } +.codicon-device-desktop:before { content: '\ea7a'; } +.codicon-file:before { content: '\ea7b'; } +.codicon-file-text:before { content: '\ea7b'; } +.codicon-more:before { content: '\ea7c'; } +.codicon-ellipsis:before { content: '\ea7c'; } +.codicon-kebab-horizontal:before { content: '\ea7c'; } +.codicon-mail-reply:before { content: '\ea7d'; } +.codicon-reply:before { content: '\ea7d'; } +.codicon-organization:before { content: '\ea7e'; } +.codicon-organization-filled:before { content: '\ea7e'; } +.codicon-organization-outline:before { content: '\ea7e'; } +.codicon-new-file:before { content: '\ea7f'; } +.codicon-file-add:before { content: '\ea7f'; } +.codicon-new-folder:before { content: '\ea80'; } +.codicon-file-directory-create:before { content: '\ea80'; } +.codicon-trash:before { content: '\ea81'; } +.codicon-trashcan:before { content: '\ea81'; } +.codicon-history:before { content: '\ea82'; } +.codicon-clock:before { content: '\ea82'; } +.codicon-folder:before { content: '\ea83'; } +.codicon-file-directory:before { content: '\ea83'; } +.codicon-symbol-folder:before { content: '\ea83'; } +.codicon-logo-github:before { content: '\ea84'; } +.codicon-mark-github:before { content: '\ea84'; } +.codicon-github:before { content: '\ea84'; } +.codicon-terminal:before { content: '\ea85'; } +.codicon-console:before { content: '\ea85'; } +.codicon-repl:before { content: '\ea85'; } +.codicon-zap:before { content: '\ea86'; } +.codicon-symbol-event:before { content: '\ea86'; } +.codicon-error:before { content: '\ea87'; } +.codicon-stop:before { content: '\ea87'; } +.codicon-variable:before { content: '\ea88'; } +.codicon-symbol-variable:before { content: '\ea88'; } +.codicon-array:before { content: '\ea8a'; } +.codicon-symbol-array:before { content: '\ea8a'; } +.codicon-symbol-module:before { content: '\ea8b'; } +.codicon-symbol-package:before { content: '\ea8b'; } +.codicon-symbol-namespace:before { content: '\ea8b'; } +.codicon-symbol-object:before { content: '\ea8b'; } +.codicon-symbol-method:before { content: '\ea8c'; } +.codicon-symbol-function:before { content: '\ea8c'; } +.codicon-symbol-constructor:before { content: '\ea8c'; } +.codicon-symbol-boolean:before { content: '\ea8f'; } +.codicon-symbol-null:before { content: '\ea8f'; } +.codicon-symbol-numeric:before { content: '\ea90'; } +.codicon-symbol-number:before { content: '\ea90'; } +.codicon-symbol-structure:before { content: '\ea91'; } +.codicon-symbol-struct:before { content: '\ea91'; } +.codicon-symbol-parameter:before { content: '\ea92'; } +.codicon-symbol-type-parameter:before { content: '\ea92'; } +.codicon-symbol-key:before { content: '\ea93'; } +.codicon-symbol-text:before { content: '\ea93'; } +.codicon-symbol-reference:before { content: '\ea94'; } +.codicon-go-to-file:before { content: '\ea94'; } +.codicon-symbol-enum:before { content: '\ea95'; } +.codicon-symbol-value:before { content: '\ea95'; } +.codicon-symbol-ruler:before { content: '\ea96'; } +.codicon-symbol-unit:before { content: '\ea96'; } +.codicon-activate-breakpoints:before { content: '\ea97'; } +.codicon-archive:before { content: '\ea98'; } +.codicon-arrow-both:before { content: '\ea99'; } +.codicon-arrow-down:before { content: '\ea9a'; } +.codicon-arrow-left:before { content: '\ea9b'; } +.codicon-arrow-right:before { content: '\ea9c'; } +.codicon-arrow-small-down:before { content: '\ea9d'; } +.codicon-arrow-small-left:before { content: '\ea9e'; } +.codicon-arrow-small-right:before { content: '\ea9f'; } +.codicon-arrow-small-up:before { content: '\eaa0'; } +.codicon-arrow-up:before { content: '\eaa1'; } +.codicon-bell:before { content: '\eaa2'; } +.codicon-bold:before { content: '\eaa3'; } +.codicon-book:before { content: '\eaa4'; } +.codicon-bookmark:before { content: '\eaa5'; } +.codicon-debug-breakpoint-conditional-unverified:before { content: '\eaa6'; } +.codicon-debug-breakpoint-conditional:before { content: '\eaa7'; } +.codicon-debug-breakpoint-conditional-disabled:before { content: '\eaa7'; } +.codicon-debug-breakpoint-data-unverified:before { content: '\eaa8'; } +.codicon-debug-breakpoint-data:before { content: '\eaa9'; } +.codicon-debug-breakpoint-data-disabled:before { content: '\eaa9'; } +.codicon-debug-breakpoint-log-unverified:before { content: '\eaaa'; } +.codicon-debug-breakpoint-log:before { content: '\eaab'; } +.codicon-debug-breakpoint-log-disabled:before { content: '\eaab'; } +.codicon-briefcase:before { content: '\eaac'; } +.codicon-broadcast:before { content: '\eaad'; } +.codicon-browser:before { content: '\eaae'; } +.codicon-bug:before { content: '\eaaf'; } +.codicon-calendar:before { content: '\eab0'; } +.codicon-case-sensitive:before { content: '\eab1'; } +.codicon-check:before { content: '\eab2'; } +.codicon-checklist:before { content: '\eab3'; } +.codicon-chevron-down:before { content: '\eab4'; } +.codicon-chevron-left:before { content: '\eab5'; } +.codicon-chevron-right:before { content: '\eab6'; } +.codicon-chevron-up:before { content: '\eab7'; } +.codicon-chrome-close:before { content: '\eab8'; } +.codicon-chrome-maximize:before { content: '\eab9'; } +.codicon-chrome-minimize:before { content: '\eaba'; } +.codicon-chrome-restore:before { content: '\eabb'; } +.codicon-circle-outline:before { content: '\eabc'; } +.codicon-circle:before { content: '\eabc'; } +.codicon-debug-breakpoint-unverified:before { content: '\eabc'; } +.codicon-terminal-decoration-incomplete:before { content: '\eabc'; } +.codicon-circle-slash:before { content: '\eabd'; } +.codicon-circuit-board:before { content: '\eabe'; } +.codicon-clear-all:before { content: '\eabf'; } +.codicon-clippy:before { content: '\eac0'; } +.codicon-close-all:before { content: '\eac1'; } +.codicon-cloud-download:before { content: '\eac2'; } +.codicon-cloud-upload:before { content: '\eac3'; } +.codicon-code:before { content: '\eac4'; } +.codicon-collapse-all:before { content: '\eac5'; } +.codicon-color-mode:before { content: '\eac6'; } +.codicon-comment-discussion:before { content: '\eac7'; } +.codicon-credit-card:before { content: '\eac9'; } +.codicon-dash:before { content: '\eacc'; } +.codicon-dashboard:before { content: '\eacd'; } +.codicon-database:before { content: '\eace'; } +.codicon-debug-continue:before { content: '\eacf'; } +.codicon-debug-disconnect:before { content: '\ead0'; } +.codicon-debug-pause:before { content: '\ead1'; } +.codicon-debug-restart:before { content: '\ead2'; } +.codicon-debug-start:before { content: '\ead3'; } +.codicon-debug-step-into:before { content: '\ead4'; } +.codicon-debug-step-out:before { content: '\ead5'; } +.codicon-debug-step-over:before { content: '\ead6'; } +.codicon-debug-stop:before { content: '\ead7'; } +.codicon-debug:before { content: '\ead8'; } +.codicon-device-camera-video:before { content: '\ead9'; } +.codicon-device-camera:before { content: '\eada'; } +.codicon-device-mobile:before { content: '\eadb'; } +.codicon-diff-added:before { content: '\eadc'; } +.codicon-diff-ignored:before { content: '\eadd'; } +.codicon-diff-modified:before { content: '\eade'; } +.codicon-diff-removed:before { content: '\eadf'; } +.codicon-diff-renamed:before { content: '\eae0'; } +.codicon-diff:before { content: '\eae1'; } +.codicon-diff-sidebyside:before { content: '\eae1'; } +.codicon-discard:before { content: '\eae2'; } +.codicon-editor-layout:before { content: '\eae3'; } +.codicon-empty-window:before { content: '\eae4'; } +.codicon-exclude:before { content: '\eae5'; } +.codicon-extensions:before { content: '\eae6'; } +.codicon-eye-closed:before { content: '\eae7'; } +.codicon-file-binary:before { content: '\eae8'; } +.codicon-file-code:before { content: '\eae9'; } +.codicon-file-media:before { content: '\eaea'; } +.codicon-file-pdf:before { content: '\eaeb'; } +.codicon-file-submodule:before { content: '\eaec'; } +.codicon-file-symlink-directory:before { content: '\eaed'; } +.codicon-file-symlink-file:before { content: '\eaee'; } +.codicon-file-zip:before { content: '\eaef'; } +.codicon-files:before { content: '\eaf0'; } +.codicon-filter:before { content: '\eaf1'; } +.codicon-flame:before { content: '\eaf2'; } +.codicon-fold-down:before { content: '\eaf3'; } +.codicon-fold-up:before { content: '\eaf4'; } +.codicon-fold:before { content: '\eaf5'; } +.codicon-folder-active:before { content: '\eaf6'; } +.codicon-folder-opened:before { content: '\eaf7'; } +.codicon-gear:before { content: '\eaf8'; } +.codicon-gift:before { content: '\eaf9'; } +.codicon-gist-secret:before { content: '\eafa'; } +.codicon-gist:before { content: '\eafb'; } +.codicon-git-commit:before { content: '\eafc'; } +.codicon-git-compare:before { content: '\eafd'; } +.codicon-compare-changes:before { content: '\eafd'; } +.codicon-git-merge:before { content: '\eafe'; } +.codicon-github-action:before { content: '\eaff'; } +.codicon-github-alt:before { content: '\eb00'; } +.codicon-globe:before { content: '\eb01'; } +.codicon-grabber:before { content: '\eb02'; } +.codicon-graph:before { content: '\eb03'; } +.codicon-gripper:before { content: '\eb04'; } +.codicon-heart:before { content: '\eb05'; } +.codicon-home:before { content: '\eb06'; } +.codicon-horizontal-rule:before { content: '\eb07'; } +.codicon-hubot:before { content: '\eb08'; } +.codicon-inbox:before { content: '\eb09'; } +.codicon-issue-reopened:before { content: '\eb0b'; } +.codicon-issues:before { content: '\eb0c'; } +.codicon-italic:before { content: '\eb0d'; } +.codicon-jersey:before { content: '\eb0e'; } +.codicon-json:before { content: '\eb0f'; } +.codicon-kebab-vertical:before { content: '\eb10'; } +.codicon-key:before { content: '\eb11'; } +.codicon-law:before { content: '\eb12'; } +.codicon-lightbulb-autofix:before { content: '\eb13'; } +.codicon-link-external:before { content: '\eb14'; } +.codicon-link:before { content: '\eb15'; } +.codicon-list-ordered:before { content: '\eb16'; } +.codicon-list-unordered:before { content: '\eb17'; } +.codicon-live-share:before { content: '\eb18'; } +.codicon-loading:before { content: '\eb19'; } +.codicon-location:before { content: '\eb1a'; } +.codicon-mail-read:before { content: '\eb1b'; } +.codicon-mail:before { content: '\eb1c'; } +.codicon-markdown:before { content: '\eb1d'; } +.codicon-megaphone:before { content: '\eb1e'; } +.codicon-mention:before { content: '\eb1f'; } +.codicon-milestone:before { content: '\eb20'; } +.codicon-git-pull-request-milestone:before { content: '\eb20'; } +.codicon-mortar-board:before { content: '\eb21'; } +.codicon-move:before { content: '\eb22'; } +.codicon-multiple-windows:before { content: '\eb23'; } +.codicon-mute:before { content: '\eb24'; } +.codicon-no-newline:before { content: '\eb25'; } +.codicon-note:before { content: '\eb26'; } +.codicon-octoface:before { content: '\eb27'; } +.codicon-open-preview:before { content: '\eb28'; } +.codicon-package:before { content: '\eb29'; } +.codicon-paintcan:before { content: '\eb2a'; } +.codicon-pin:before { content: '\eb2b'; } +.codicon-play:before { content: '\eb2c'; } +.codicon-run:before { content: '\eb2c'; } +.codicon-plug:before { content: '\eb2d'; } +.codicon-preserve-case:before { content: '\eb2e'; } +.codicon-preview:before { content: '\eb2f'; } +.codicon-project:before { content: '\eb30'; } +.codicon-pulse:before { content: '\eb31'; } +.codicon-question:before { content: '\eb32'; } +.codicon-quote:before { content: '\eb33'; } +.codicon-radio-tower:before { content: '\eb34'; } +.codicon-reactions:before { content: '\eb35'; } +.codicon-references:before { content: '\eb36'; } +.codicon-refresh:before { content: '\eb37'; } +.codicon-regex:before { content: '\eb38'; } +.codicon-remote-explorer:before { content: '\eb39'; } +.codicon-remote:before { content: '\eb3a'; } +.codicon-remove:before { content: '\eb3b'; } +.codicon-replace-all:before { content: '\eb3c'; } +.codicon-replace:before { content: '\eb3d'; } +.codicon-repo-clone:before { content: '\eb3e'; } +.codicon-repo-force-push:before { content: '\eb3f'; } +.codicon-repo-pull:before { content: '\eb40'; } +.codicon-repo-push:before { content: '\eb41'; } +.codicon-report:before { content: '\eb42'; } +.codicon-request-changes:before { content: '\eb43'; } +.codicon-rocket:before { content: '\eb44'; } +.codicon-root-folder-opened:before { content: '\eb45'; } +.codicon-root-folder:before { content: '\eb46'; } +.codicon-rss:before { content: '\eb47'; } +.codicon-ruby:before { content: '\eb48'; } +.codicon-save-all:before { content: '\eb49'; } +.codicon-save-as:before { content: '\eb4a'; } +.codicon-save:before { content: '\eb4b'; } +.codicon-screen-full:before { content: '\eb4c'; } +.codicon-screen-normal:before { content: '\eb4d'; } +.codicon-search-stop:before { content: '\eb4e'; } +.codicon-server:before { content: '\eb50'; } +.codicon-settings-gear:before { content: '\eb51'; } +.codicon-settings:before { content: '\eb52'; } +.codicon-shield:before { content: '\eb53'; } +.codicon-smiley:before { content: '\eb54'; } +.codicon-sort-precedence:before { content: '\eb55'; } +.codicon-split-horizontal:before { content: '\eb56'; } +.codicon-split-vertical:before { content: '\eb57'; } +.codicon-squirrel:before { content: '\eb58'; } +.codicon-star-full:before { content: '\eb59'; } +.codicon-star-half:before { content: '\eb5a'; } +.codicon-symbol-class:before { content: '\eb5b'; } +.codicon-symbol-color:before { content: '\eb5c'; } +.codicon-symbol-constant:before { content: '\eb5d'; } +.codicon-symbol-enum-member:before { content: '\eb5e'; } +.codicon-symbol-field:before { content: '\eb5f'; } +.codicon-symbol-file:before { content: '\eb60'; } +.codicon-symbol-interface:before { content: '\eb61'; } +.codicon-symbol-keyword:before { content: '\eb62'; } +.codicon-symbol-misc:before { content: '\eb63'; } +.codicon-symbol-operator:before { content: '\eb64'; } +.codicon-symbol-property:before { content: '\eb65'; } +.codicon-wrench:before { content: '\eb65'; } +.codicon-wrench-subaction:before { content: '\eb65'; } +.codicon-symbol-snippet:before { content: '\eb66'; } +.codicon-tasklist:before { content: '\eb67'; } +.codicon-telescope:before { content: '\eb68'; } +.codicon-text-size:before { content: '\eb69'; } +.codicon-three-bars:before { content: '\eb6a'; } +.codicon-thumbsdown:before { content: '\eb6b'; } +.codicon-thumbsup:before { content: '\eb6c'; } +.codicon-tools:before { content: '\eb6d'; } +.codicon-triangle-down:before { content: '\eb6e'; } +.codicon-triangle-left:before { content: '\eb6f'; } +.codicon-triangle-right:before { content: '\eb70'; } +.codicon-triangle-up:before { content: '\eb71'; } +.codicon-twitter:before { content: '\eb72'; } +.codicon-unfold:before { content: '\eb73'; } +.codicon-unlock:before { content: '\eb74'; } +.codicon-unmute:before { content: '\eb75'; } +.codicon-unverified:before { content: '\eb76'; } +.codicon-verified:before { content: '\eb77'; } +.codicon-versions:before { content: '\eb78'; } +.codicon-vm-active:before { content: '\eb79'; } +.codicon-vm-outline:before { content: '\eb7a'; } +.codicon-vm-running:before { content: '\eb7b'; } +.codicon-watch:before { content: '\eb7c'; } +.codicon-whitespace:before { content: '\eb7d'; } +.codicon-whole-word:before { content: '\eb7e'; } +.codicon-window:before { content: '\eb7f'; } +.codicon-word-wrap:before { content: '\eb80'; } +.codicon-zoom-in:before { content: '\eb81'; } +.codicon-zoom-out:before { content: '\eb82'; } +.codicon-list-filter:before { content: '\eb83'; } +.codicon-list-flat:before { content: '\eb84'; } +.codicon-list-selection:before { content: '\eb85'; } +.codicon-selection:before { content: '\eb85'; } +.codicon-list-tree:before { content: '\eb86'; } +.codicon-debug-breakpoint-function-unverified:before { content: '\eb87'; } +.codicon-debug-breakpoint-function:before { content: '\eb88'; } +.codicon-debug-breakpoint-function-disabled:before { content: '\eb88'; } +.codicon-debug-stackframe-active:before { content: '\eb89'; } +.codicon-circle-small-filled:before { content: '\eb8a'; } +.codicon-debug-stackframe-dot:before { content: '\eb8a'; } +.codicon-terminal-decoration-mark:before { content: '\eb8a'; } +.codicon-debug-stackframe:before { content: '\eb8b'; } +.codicon-debug-stackframe-focused:before { content: '\eb8b'; } +.codicon-debug-breakpoint-unsupported:before { content: '\eb8c'; } +.codicon-symbol-string:before { content: '\eb8d'; } +.codicon-debug-reverse-continue:before { content: '\eb8e'; } +.codicon-debug-step-back:before { content: '\eb8f'; } +.codicon-debug-restart-frame:before { content: '\eb90'; } +.codicon-debug-alt:before { content: '\eb91'; } +.codicon-call-incoming:before { content: '\eb92'; } +.codicon-call-outgoing:before { content: '\eb93'; } +.codicon-menu:before { content: '\eb94'; } +.codicon-expand-all:before { content: '\eb95'; } +.codicon-feedback:before { content: '\eb96'; } +.codicon-git-pull-request-reviewer:before { content: '\eb96'; } +.codicon-group-by-ref-type:before { content: '\eb97'; } +.codicon-ungroup-by-ref-type:before { content: '\eb98'; } +.codicon-account:before { content: '\eb99'; } +.codicon-git-pull-request-assignee:before { content: '\eb99'; } +.codicon-bell-dot:before { content: '\eb9a'; } +.codicon-debug-console:before { content: '\eb9b'; } +.codicon-library:before { content: '\eb9c'; } +.codicon-output:before { content: '\eb9d'; } +.codicon-run-all:before { content: '\eb9e'; } +.codicon-sync-ignored:before { content: '\eb9f'; } +.codicon-pinned:before { content: '\eba0'; } +.codicon-github-inverted:before { content: '\eba1'; } +.codicon-server-process:before { content: '\eba2'; } +.codicon-server-environment:before { content: '\eba3'; } +.codicon-pass:before { content: '\eba4'; } +.codicon-issue-closed:before { content: '\eba4'; } +.codicon-stop-circle:before { content: '\eba5'; } +.codicon-play-circle:before { content: '\eba6'; } +.codicon-record:before { content: '\eba7'; } +.codicon-debug-alt-small:before { content: '\eba8'; } +.codicon-vm-connect:before { content: '\eba9'; } +.codicon-cloud:before { content: '\ebaa'; } +.codicon-merge:before { content: '\ebab'; } +.codicon-export:before { content: '\ebac'; } +.codicon-graph-left:before { content: '\ebad'; } +.codicon-magnet:before { content: '\ebae'; } +.codicon-notebook:before { content: '\ebaf'; } +.codicon-redo:before { content: '\ebb0'; } +.codicon-check-all:before { content: '\ebb1'; } +.codicon-pinned-dirty:before { content: '\ebb2'; } +.codicon-pass-filled:before { content: '\ebb3'; } +.codicon-circle-large-filled:before { content: '\ebb4'; } +.codicon-circle-large:before { content: '\ebb5'; } +.codicon-circle-large-outline:before { content: '\ebb5'; } +.codicon-combine:before { content: '\ebb6'; } +.codicon-gather:before { content: '\ebb6'; } +.codicon-table:before { content: '\ebb7'; } +.codicon-variable-group:before { content: '\ebb8'; } +.codicon-type-hierarchy:before { content: '\ebb9'; } +.codicon-type-hierarchy-sub:before { content: '\ebba'; } +.codicon-type-hierarchy-super:before { content: '\ebbb'; } +.codicon-git-pull-request-create:before { content: '\ebbc'; } +.codicon-run-above:before { content: '\ebbd'; } +.codicon-run-below:before { content: '\ebbe'; } +.codicon-notebook-template:before { content: '\ebbf'; } +.codicon-debug-rerun:before { content: '\ebc0'; } +.codicon-workspace-trusted:before { content: '\ebc1'; } +.codicon-workspace-untrusted:before { content: '\ebc2'; } +.codicon-workspace-unknown:before { content: '\ebc3'; } +.codicon-terminal-cmd:before { content: '\ebc4'; } +.codicon-terminal-debian:before { content: '\ebc5'; } +.codicon-terminal-linux:before { content: '\ebc6'; } +.codicon-terminal-powershell:before { content: '\ebc7'; } +.codicon-terminal-tmux:before { content: '\ebc8'; } +.codicon-terminal-ubuntu:before { content: '\ebc9'; } +.codicon-terminal-bash:before { content: '\ebca'; } +.codicon-arrow-swap:before { content: '\ebcb'; } +.codicon-copy:before { content: '\ebcc'; } +.codicon-person-add:before { content: '\ebcd'; } +.codicon-filter-filled:before { content: '\ebce'; } +.codicon-wand:before { content: '\ebcf'; } +.codicon-debug-line-by-line:before { content: '\ebd0'; } +.codicon-inspect:before { content: '\ebd1'; } +.codicon-layers:before { content: '\ebd2'; } +.codicon-layers-dot:before { content: '\ebd3'; } +.codicon-layers-active:before { content: '\ebd4'; } +.codicon-compass:before { content: '\ebd5'; } +.codicon-compass-dot:before { content: '\ebd6'; } +.codicon-compass-active:before { content: '\ebd7'; } +.codicon-azure:before { content: '\ebd8'; } +.codicon-issue-draft:before { content: '\ebd9'; } +.codicon-git-pull-request-closed:before { content: '\ebda'; } +.codicon-git-pull-request-draft:before { content: '\ebdb'; } +.codicon-debug-all:before { content: '\ebdc'; } +.codicon-debug-coverage:before { content: '\ebdd'; } +.codicon-run-errors:before { content: '\ebde'; } +.codicon-folder-library:before { content: '\ebdf'; } +.codicon-debug-continue-small:before { content: '\ebe0'; } +.codicon-beaker-stop:before { content: '\ebe1'; } +.codicon-graph-line:before { content: '\ebe2'; } +.codicon-graph-scatter:before { content: '\ebe3'; } +.codicon-pie-chart:before { content: '\ebe4'; } +.codicon-bracket:before { content: '\eb0f'; } +.codicon-bracket-dot:before { content: '\ebe5'; } +.codicon-bracket-error:before { content: '\ebe6'; } +.codicon-lock-small:before { content: '\ebe7'; } +.codicon-azure-devops:before { content: '\ebe8'; } +.codicon-verified-filled:before { content: '\ebe9'; } +.codicon-newline:before { content: '\ebea'; } +.codicon-layout:before { content: '\ebeb'; } +.codicon-layout-activitybar-left:before { content: '\ebec'; } +.codicon-layout-activitybar-right:before { content: '\ebed'; } +.codicon-layout-panel-left:before { content: '\ebee'; } +.codicon-layout-panel-center:before { content: '\ebef'; } +.codicon-layout-panel-justify:before { content: '\ebf0'; } +.codicon-layout-panel-right:before { content: '\ebf1'; } +.codicon-layout-panel:before { content: '\ebf2'; } +.codicon-layout-sidebar-left:before { content: '\ebf3'; } +.codicon-layout-sidebar-right:before { content: '\ebf4'; } +.codicon-layout-statusbar:before { content: '\ebf5'; } +.codicon-layout-menubar:before { content: '\ebf6'; } +.codicon-layout-centered:before { content: '\ebf7'; } +.codicon-target:before { content: '\ebf8'; } +.codicon-indent:before { content: '\ebf9'; } +.codicon-record-small:before { content: '\ebfa'; } +.codicon-error-small:before { content: '\ebfb'; } +.codicon-terminal-decoration-error:before { content: '\ebfb'; } +.codicon-arrow-circle-down:before { content: '\ebfc'; } +.codicon-arrow-circle-left:before { content: '\ebfd'; } +.codicon-arrow-circle-right:before { content: '\ebfe'; } +.codicon-arrow-circle-up:before { content: '\ebff'; } +.codicon-layout-sidebar-right-off:before { content: '\ec00'; } +.codicon-layout-panel-off:before { content: '\ec01'; } +.codicon-layout-sidebar-left-off:before { content: '\ec02'; } +.codicon-blank:before { content: '\ec03'; } +.codicon-heart-filled:before { content: '\ec04'; } +.codicon-map:before { content: '\ec05'; } +.codicon-map-horizontal:before { content: '\ec05'; } +.codicon-fold-horizontal:before { content: '\ec05'; } +.codicon-map-filled:before { content: '\ec06'; } +.codicon-map-horizontal-filled:before { content: '\ec06'; } +.codicon-fold-horizontal-filled:before { content: '\ec06'; } +.codicon-circle-small:before { content: '\ec07'; } +.codicon-bell-slash:before { content: '\ec08'; } +.codicon-bell-slash-dot:before { content: '\ec09'; } +.codicon-comment-unresolved:before { content: '\ec0a'; } +.codicon-git-pull-request-go-to-changes:before { content: '\ec0b'; } +.codicon-git-pull-request-new-changes:before { content: '\ec0c'; } +.codicon-search-fuzzy:before { content: '\ec0d'; } +.codicon-comment-draft:before { content: '\ec0e'; } +.codicon-send:before { content: '\ec0f'; } +.codicon-sparkle:before { content: '\ec10'; } +.codicon-insert:before { content: '\ec11'; } +.codicon-mic:before { content: '\ec12'; } +.codicon-thumbsdown-filled:before { content: '\ec13'; } +.codicon-thumbsup-filled:before { content: '\ec14'; } +.codicon-coffee:before { content: '\ec15'; } +.codicon-snake:before { content: '\ec16'; } +.codicon-game:before { content: '\ec17'; } +.codicon-vr:before { content: '\ec18'; } +.codicon-chip:before { content: '\ec19'; } +.codicon-piano:before { content: '\ec1a'; } +.codicon-music:before { content: '\ec1b'; } +.codicon-mic-filled:before { content: '\ec1c'; } +.codicon-repo-fetch:before { content: '\ec1d'; } +.codicon-copilot:before { content: '\ec1e'; } +.codicon-lightbulb-sparkle:before { content: '\ec1f'; } +.codicon-robot:before { content: '\ec20'; } +.codicon-sparkle-filled:before { content: '\ec21'; } +.codicon-diff-single:before { content: '\ec22'; } +.codicon-diff-multiple:before { content: '\ec23'; } +.codicon-surround-with:before { content: '\ec24'; } +.codicon-share:before { content: '\ec25'; } +.codicon-git-stash:before { content: '\ec26'; } +.codicon-git-stash-apply:before { content: '\ec27'; } +.codicon-git-stash-pop:before { content: '\ec28'; } +.codicon-vscode:before { content: '\ec29'; } +.codicon-vscode-insiders:before { content: '\ec2a'; } +.codicon-code-oss:before { content: '\ec2b'; } +.codicon-run-coverage:before { content: '\ec2c'; } +.codicon-run-all-coverage:before { content: '\ec2d'; } +.codicon-coverage:before { content: '\ec2e'; } +.codicon-github-project:before { content: '\ec2f'; } +.codicon-map-vertical:before { content: '\ec30'; } +.codicon-fold-vertical:before { content: '\ec30'; } +.codicon-map-vertical-filled:before { content: '\ec31'; } +.codicon-fold-vertical-filled:before { content: '\ec31'; } +.codicon-go-to-search:before { content: '\ec32'; } +.codicon-percentage:before { content: '\ec33'; } +.codicon-sort-percentage:before { content: '\ec33'; } +.codicon-attach:before { content: '\ec34'; } +.codicon-git-fetch:before { content: '\f101'; } diff --git a/tests/assets/codicon.ttf b/tests/assets/codicon.ttf new file mode 100644 index 0000000000..27ee4c68ca Binary files /dev/null and b/tests/assets/codicon.ttf differ diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index 766df6d485..878f48439e 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -15,26 +15,373 @@ * limitations under the License. */ +import type { Locator } from '@playwright/test'; import { test as it, expect } from './pageTest'; -it('should snapshot the check box @smoke', async ({ page }) => { - await page.setContent(``); - expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox'); +function unshift(snapshot: string): string { + const lines = snapshot.split('\n'); + let whitespacePrefixLength = 100; + for (const line of lines) { + if (!line.trim()) + continue; + const match = line.match(/^(\s*)/); + if (match && match[1].length < whitespacePrefixLength) + whitespacePrefixLength = match[1].length; + break; + } + return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); +} + +async function checkAndMatchSnapshot(locator: Locator, snapshot: string) { + expect.soft(await locator.ariaSnapshot()).toBe(unshift(snapshot)); + await expect.soft(locator).toMatchAriaSnapshot(snapshot); +} + +it('should snapshot', async ({ page }) => { + await page.setContent(`

title

`); + await checkAndMatchSnapshot(page.locator('body'), ` + - heading "title" + `); }); -it('should snapshot nested element', async ({ page }) => { +it('should snapshot list', async ({ page }) => { await page.setContent(` -
- -
`); - expect(await page.locator('body').ariaSnapshot()).toBe('- checkbox'); +

title

+

title 2

+ `); + await checkAndMatchSnapshot(page.locator('body'), ` + - heading "title" + - heading "title 2" + `); }); -it('should snapshot fragment', async ({ page }) => { +it('should snapshot list with accessible name', async ({ page }) => { await page.setContent(` -
- Link - Link -
`); - expect(await page.locator('body').ariaSnapshot()).toBe(`- link "Link"\n- link "Link"`); +
    +
  • one
  • +
  • two
  • +
+ `); + await checkAndMatchSnapshot(page.locator('body'), ` + - list "my list": + - listitem: one + - listitem: two + `); +}); + +it('should snapshot complex', async ({ page }) => { + await page.setContent(` + + `); + await checkAndMatchSnapshot(page.locator('body'), ` + - list: + - listitem: + - link "link" + `); +}); + +it('should allow text nodes', async ({ page }) => { + await page.setContent(` +

Microsoft

+
Open source projects and samples from Microsoft
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - heading "Microsoft" + - text: Open source projects and samples from Microsoft + `); +}); + +it('should snapshot details visibility', async ({ page }) => { + await page.setContent(` +
+ Summary +
Details
+
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - group: Summary + `); +}); + +it('should snapshot integration', async ({ page }) => { + await page.setContent(` +

Microsoft

+
Open source projects and samples from Microsoft
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - heading "Microsoft" + - text: Open source projects and samples from Microsoft + - list: + - listitem: + - group: Verified + - listitem: + - link "Sponsor" + `); +}); + +it('should support multiline text', async ({ page }) => { + await page.setContent(` +

+ Line 1 + Line 2 + Line 3 +

+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - paragraph: Line 1 Line 2 Line 3 + `); + await expect(page.locator('body')).toMatchAriaSnapshot(` + - paragraph: | + Line 1 + Line 2 + Line 3 + `); +}); + +it('should concatenate span text', async ({ page }) => { + await page.setContent(` + One Two Three + `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - text: One Two Three + `); +}); + +it('should concatenate span text 2', async ({ page }) => { + await page.setContent(` + One Two Three + `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - text: One Two Three + `); +}); + +it('should concatenate div text with spaces', async ({ page }) => { + await page.setContent(` +
One
Two
Three
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - text: One Two Three + `); +}); + +it('should include pseudo in text', async ({ page }) => { + await page.setContent(` + + + hello +
hello
+
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - link "worldhello hellobye" + `); +}); + +it('should not include hidden pseudo in text', async ({ page }) => { + await page.setContent(` + + + hello +
hello
+
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - link "hello hello" + `); +}); + +it('should include new line for block pseudo', async ({ page }) => { + await page.setContent(` + + + hello +
hello
+
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - link "world hello hello bye" + `); +}); + +it('should work with slots', async ({ page }) => { + // Text "foo" is assigned to the slot, should not be used twice. + await page.setContent(` + + + `); + await checkAndMatchSnapshot(page.locator('body'), ` + - button "foo" + `); + + // Text "foo" is assigned to the slot, should be used instead of slot content. + await page.setContent(` +
foo
+ + `); + await checkAndMatchSnapshot(page.locator('body'), ` + - button "foo" + `); + + // Nothing is assigned to the slot, should use slot content. + await page.setContent(` +
+ + `); + await checkAndMatchSnapshot(page.locator('body'), ` + - button "pre" + `); +}); + +it('should snapshot inner text', async ({ page }) => { + await page.setContent(` +
+
+
+ a.test.ts +
+
+ + + +
+
+
+
+
+
+ snapshot +
+
30ms
+
+ + + +
+
+
+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - listitem: + - text: a.test.ts + - button "Run" + - button "Show source" + - button "Watch" + - listitem: + - text: snapshot 30ms + - button "Run" + - button "Show source" + - button "Watch" + `); +}); + +it('should include pseudo codepoints', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(` + +

hello

+ `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - paragraph: \ueab2hello + `); }); diff --git a/tests/page/to-match-aria-snapshot.spec.ts b/tests/page/to-match-aria-snapshot.spec.ts index 5e58ba94e0..fa573f2704 100644 --- a/tests/page/to-match-aria-snapshot.spec.ts +++ b/tests/page/to-match-aria-snapshot.spec.ts @@ -94,7 +94,7 @@ test('should allow text nodes', async ({ page }) => { `); }); -test('details visibility', async ({ page, browserName }) => { +test('details visibility', async ({ page }) => { await page.setContent(`
Summary @@ -107,7 +107,7 @@ test('details visibility', async ({ page, browserName }) => { `); }); -test('integration test', async ({ page, browserName }) => { +test('integration test', async ({ page }) => { await page.setContent(`

Microsoft

Open source projects and samples from Microsoft
diff --git a/tests/playwright-test/stable-test-runner/package-lock.json b/tests/playwright-test/stable-test-runner/package-lock.json index 180f4d9b33..df6792d59d 100644 --- a/tests/playwright-test/stable-test-runner/package-lock.json +++ b/tests/playwright-test/stable-test-runner/package-lock.json @@ -5,16 +5,15 @@ "packages": { "": { "dependencies": { - "@playwright/test": "1.48.0-beta-1728384960000" + "@playwright/test": "1.49.0-alpha-2024-10-17" } }, "node_modules/@playwright/test": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-bqQorY7LKVldgwAsUbjULdwKEoUlZ8OOHRZmM/1XyGiGqJwzTGdr0x8Ss312BvKddAh+5pz8cbaPopw10Rp3Ng==", - "license": "Apache-2.0", + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-HLZY3sM6xt9Wi8K09zPwjJQtcUBZNBcNSIVoMZhtJM3+TikCKx4SiJ3P8vbSlk7Tm3s2oqlS+wA181IxhbTGBA==", "dependencies": { - "playwright": "1.48.0-beta-1728384960000" + "playwright": "1.49.0-alpha-2024-10-17" }, "bin": { "playwright": "cli.js" @@ -28,7 +27,6 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -38,12 +36,11 @@ } }, "node_modules/playwright": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-5pIZTwoktOGYJL+YpF2RNhGzVUY6rA/ceQAT0lEQSZaL55MKUzraD2FAoZoBnz84cIIks2ZSlXt8j5mJ5xXt8g==", - "license": "Apache-2.0", + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-IgcLunnpocVS/AEq2lcftVOu0DGQzFm1Qt25SCJsrVvKVe83ElKXZYskPz7yA0HeuOVxQyN69EDWI09ph7lfoQ==", "dependencies": { - "playwright-core": "1.48.0-beta-1728384960000" + "playwright-core": "1.49.0-alpha-2024-10-17" }, "bin": { "playwright": "cli.js" @@ -56,10 +53,9 @@ } }, "node_modules/playwright-core": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-atIhpuvqvVEW5luPhwzhdcXsGdPvzOBLXAg3+MvOLY+6Q4JcTfXMTtTmltP+llUV+LAgj38foQz+6tKTzNMlWg==", - "license": "Apache-2.0", + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-XLTKmPBm2ZIOXBckXtiimSOIjQsYy8MqEP9CsHSgytsP0E+j/44v1BuwHOOMaG8sfjcuZLZ1QdFidnl07A9wSg==", "bin": { "playwright-core": "cli.js" }, @@ -70,11 +66,11 @@ }, "dependencies": { "@playwright/test": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-bqQorY7LKVldgwAsUbjULdwKEoUlZ8OOHRZmM/1XyGiGqJwzTGdr0x8Ss312BvKddAh+5pz8cbaPopw10Rp3Ng==", + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-HLZY3sM6xt9Wi8K09zPwjJQtcUBZNBcNSIVoMZhtJM3+TikCKx4SiJ3P8vbSlk7Tm3s2oqlS+wA181IxhbTGBA==", "requires": { - "playwright": "1.48.0-beta-1728384960000" + "playwright": "1.49.0-alpha-2024-10-17" } }, "fsevents": { @@ -84,18 +80,18 @@ "optional": true }, "playwright": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-5pIZTwoktOGYJL+YpF2RNhGzVUY6rA/ceQAT0lEQSZaL55MKUzraD2FAoZoBnz84cIIks2ZSlXt8j5mJ5xXt8g==", + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-IgcLunnpocVS/AEq2lcftVOu0DGQzFm1Qt25SCJsrVvKVe83ElKXZYskPz7yA0HeuOVxQyN69EDWI09ph7lfoQ==", "requires": { "fsevents": "2.3.2", - "playwright-core": "1.48.0-beta-1728384960000" + "playwright-core": "1.49.0-alpha-2024-10-17" } }, "playwright-core": { - "version": "1.48.0-beta-1728384960000", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0-beta-1728384960000.tgz", - "integrity": "sha512-atIhpuvqvVEW5luPhwzhdcXsGdPvzOBLXAg3+MvOLY+6Q4JcTfXMTtTmltP+llUV+LAgj38foQz+6tKTzNMlWg==" + "version": "1.49.0-alpha-2024-10-17", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0-alpha-2024-10-17.tgz", + "integrity": "sha512-XLTKmPBm2ZIOXBckXtiimSOIjQsYy8MqEP9CsHSgytsP0E+j/44v1BuwHOOMaG8sfjcuZLZ1QdFidnl07A9wSg==" } } } diff --git a/tests/playwright-test/stable-test-runner/package.json b/tests/playwright-test/stable-test-runner/package.json index 3e32d0bbb7..14625ebe6d 100644 --- a/tests/playwright-test/stable-test-runner/package.json +++ b/tests/playwright-test/stable-test-runner/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "@playwright/test": "1.48.0-beta-1728384960000" + "@playwright/test": "1.49.0-alpha-2024-10-17" } }