diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts index a8d9e81bcd..82f3119d9c 100644 --- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts @@ -107,7 +107,7 @@ export function generateAriaTree(rootElement: Element): AriaNode { function toAriaNode(element: Element): AriaNode | null { const role = roleUtils.getAriaRole(element); - if (!role) + if (!role || role === 'presentation' || role === 'none') return null; const name = roleUtils.getElementAccessibleName(element, false) || ''; @@ -168,7 +168,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) { visit(rootA11yNode); } -const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' '); +const normalizeWhitespaceWithin = (text: string) => text.replace(/[\u200b\s\t\r\n]+/g, ' '); function matchesText(text: string | undefined, template: RegExp | string | undefined) { if (!template) @@ -251,11 +251,12 @@ function matchesNodeDeep(root: AriaNode, template: AriaTemplateNode): boolean { return !!results.length; } -export function renderAriaTree(ariaNode: AriaNode): string { +export function renderAriaTree(ariaNode: AriaNode, options?: { noText?: boolean }): string { const lines: string[] = []; const visit = (ariaNode: AriaNode | string, indent: string) => { if (typeof ariaNode === 'string') { - lines.push(indent + '- text: ' + quoteYamlString(ariaNode)); + if (!options?.noText) + lines.push(indent + '- text: ' + quoteYamlString(ariaNode)); return; } let line = `${indent}- ${ariaNode.role}`; @@ -282,7 +283,8 @@ export function renderAriaTree(ariaNode: AriaNode): string { if (!ariaNode.children.length) { lines.push(line); } else if (ariaNode.children.length === 1 && typeof ariaNode.children[0] === 'string') { - line += ': ' + quoteYamlString(ariaNode.children[0]); + if (!options?.noText) + line += ': ' + quoteYamlString(ariaNode.children[0]); lines.push(line); } else { lines.push(line + ':'); diff --git a/packages/playwright/src/matchers/matcherHint.ts b/packages/playwright/src/matchers/matcherHint.ts index c4f5afd4b4..fc195474e7 100644 --- a/packages/playwright/src/matchers/matcherHint.ts +++ b/packages/playwright/src/matchers/matcherHint.ts @@ -33,7 +33,7 @@ export function matcherHint(state: ExpectMatcherState, locator: Locator | undefi export type MatcherResult = { name: string; - expected: E; + expected?: E; message: () => string; pass: boolean; actual?: A; diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 2c92d562d6..496c1b9a08 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -34,10 +34,10 @@ export async function toMatchAriaSnapshot( const testInfo = currentTestInfo(); if (!testInfo) - throw new Error(`toMatchSnapshot() must be called during the test`); + throw new Error(`toMatchAriaSnapshot() must be called during the test`); if (testInfo._projectInternal.ignoreSnapshots) - return { pass: !this.isNot, message: () => '', name: 'toMatchSnapshot', expected }; + return { pass: !this.isNot, message: () => '', name: 'toMatchAriaSnapshot', expected }; const updateSnapshots = testInfo.config.updateSnapshots; @@ -54,13 +54,27 @@ export async function toMatchAriaSnapshot( ].join('\n\n')); } + const generateMissingBaseline = updateSnapshots === 'missing' && !expected; + const generateNewBaseline = updateSnapshots === 'all' || generateMissingBaseline; + + if (generateMissingBaseline) { + if (this.isNot) { + const message = `Matchers using ".not" can't generate new baselines`; + return { pass: this.isNot, message: () => message, name: 'toMatchAriaSnapshot' }; + } else { + // When generating new baseline, run entire pipeline against impossible match. + expected = `- none "Generating new baseline"`; + } + } + const timeout = options.timeout ?? this.timeout; + expected = unshift(expected); 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; - const escapedExpected = unshift(escapePrivateUsePoints(expected)); - const escapedReceived = unshift(escapePrivateUsePoints(received)); + const escapedExpected = escapePrivateUsePoints(expected); + const escapedReceived = escapePrivateUsePoints(received); const message = () => { if (pass) { if (notFound) @@ -75,10 +89,10 @@ export async function toMatchAriaSnapshot( } }; - let suggestedRebaseline: string | undefined; - if (!this.isNot && pass === this.isNot) { - if (updateSnapshots === 'all' || (updateSnapshots === 'missing' && !expected.trim())) - suggestedRebaseline = `toMatchAriaSnapshot(\`\n${unshift(received, '${indent} ')}\n\${indent}\`)`; + if (!this.isNot && pass === this.isNot && generateNewBaseline) { + // Only rebaseline failed snapshots. + const suggestedRebaseline = `toMatchAriaSnapshot(\`\n${indent(received, '${indent} ')}\n\${indent}\`)`; + return { pass: this.isNot, message: () => '', name: 'toMatchAriaSnapshot', suggestedRebaseline }; } return { @@ -88,7 +102,6 @@ export async function toMatchAriaSnapshot( pass, actual: received, log, - suggestedRebaseline, timeout: timedOut ? timeout : undefined, }; } @@ -97,7 +110,7 @@ function escapePrivateUsePoints(str: string) { return str.replace(/[\uE000-\uF8FF]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`); } -function unshift(snapshot: string, indent: string = ''): string { +function unshift(snapshot: string): string { const lines = snapshot.split('\n'); let whitespacePrefixLength = 100; for (const line of lines) { @@ -108,5 +121,9 @@ function unshift(snapshot: string, indent: string = ''): string { whitespacePrefixLength = match[1].length; break; } - return lines.filter(t => t.trim()).map(line => indent + line.substring(whitespacePrefixLength)).join('\n'); + return lines.filter(t => t.trim()).map(line => line.substring(whitespacePrefixLength)).join('\n'); +} + +function indent(snapshot: string, indent: string): string { + return snapshot.split('\n').map(line => indent + line).join('\n'); } diff --git a/packages/trace-viewer/src/ui/sourceTab.tsx b/packages/trace-viewer/src/ui/sourceTab.tsx index d130499207..1dd9170f67 100644 --- a/packages/trace-viewer/src/ui/sourceTab.tsx +++ b/packages/trace-viewer/src/ui/sourceTab.tsx @@ -111,7 +111,7 @@ export const SourceTab: React.FunctionComponent<{ {location && } } - + } sidebar={} />; diff --git a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx index 7671c2e9ba..314fce3a96 100644 --- a/packages/trace-viewer/src/ui/uiModeFiltersView.tsx +++ b/packages/trace-viewer/src/ui/uiModeFiltersView.tsx @@ -58,9 +58,9 @@ export const FiltersView: React.FC<{ Projects: {projectsLine} {expanded &&
-
+
{[...statusFilters.entries()].map(([status, value]) => { - return
+ return
; })}
-
+
{[...projectFilters.entries()].map(([projectName, value]) => { - return
+ return