diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index 4e12d18fe6..f6e5859d4f 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -23,7 +23,8 @@ import { addSuffixToFilePath, trimLongString, callLogText, expectTypes, - sanitizeFilePathBeforeExtension } from '../util'; + sanitizeFilePathBeforeExtension, + windowsFilesystemFriendlyLength } from '../util'; import { colors } from 'playwright-core/lib/utilsBundle'; import fs from 'fs'; import path from 'path'; @@ -74,7 +75,7 @@ const NonConfigProperties: (keyof ToHaveScreenshotOptions)[] = [ class SnapshotHelper { readonly testInfo: TestInfoImpl; - readonly snapshotName: string; + readonly outputBaseName: string; readonly legacyExpectedPath: string; readonly previousPath: string; readonly snapshotPath: string; @@ -128,9 +129,9 @@ class SnapshotHelper { ...testInfo.titlePath.slice(1), ++snapshotNames.anonymousSnapshotIndex, ].join(' '); - name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension; - inputPathSegments = [name]; - this.snapshotName = name; + inputPathSegments = [sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension]; + // Trim the output file paths more aggresively to avoid hitting Windows filesystem limits. + this.outputBaseName = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec, windowsFilesystemFriendlyLength)) + '.' + anonymousSnapshotExtension; } else { // We intentionally do not sanitize user-provided array of segments, but for backwards // compatibility we do sanitize the name if it is a single string. @@ -140,13 +141,13 @@ class SnapshotHelper { snapshotNames.namedSnapshotIndex[joinedName] = (snapshotNames.namedSnapshotIndex[joinedName] || 0) + 1; const index = snapshotNames.namedSnapshotIndex[joinedName]; if (index > 1) - this.snapshotName = addSuffixToFilePath(joinedName, `-${index - 1}`); + this.outputBaseName = addSuffixToFilePath(joinedName, `-${index - 1}`); else - this.snapshotName = joinedName; + this.outputBaseName = joinedName; } this.snapshotPath = testInfo.snapshotPath(...inputPathSegments); - this.legacyExpectedPath = addSuffixToFilePath(testInfo._getOutputPath(...inputPathSegments), '-expected'); - const outputFile = testInfo._getOutputPath(sanitizeFilePathBeforeExtension(this.snapshotName)); + const outputFile = testInfo._getOutputPath(sanitizeFilePathBeforeExtension(this.outputBaseName)); + this.legacyExpectedPath = addSuffixToFilePath(outputFile, '-expected'); this.previousPath = addSuffixToFilePath(outputFile, '-previous'); this.actualPath = addSuffixToFilePath(outputFile, '-actual'); this.diffPath = addSuffixToFilePath(outputFile, '-diff'); @@ -222,7 +223,7 @@ class SnapshotHelper { if (isWriteMissingMode) { writeFileSync(this.snapshotPath, actual); writeFileSync(this.actualPath, actual); - this.testInfo.attachments.push({ name: addSuffixToFilePath(this.snapshotName, '-actual'), contentType: this.mimeType, path: this.actualPath }); + this.testInfo.attachments.push({ name: addSuffixToFilePath(this.outputBaseName, '-actual'), contentType: this.mimeType, path: this.actualPath }); } const message = `A snapshot doesn't exist at ${this.snapshotPath}${isWriteMissingMode ? ', writing actual.' : '.'}`; if (this.updateSnapshots === 'all') { @@ -256,22 +257,22 @@ class SnapshotHelper { // Copy the expectation inside the `test-results/` folder for backwards compatibility, // so that one can upload `test-results/` directory and have all the data inside. writeFileSync(this.legacyExpectedPath, expected); - this.testInfo.attachments.push({ name: addSuffixToFilePath(this.snapshotName, '-expected'), contentType: this.mimeType, path: this.snapshotPath }); + this.testInfo.attachments.push({ name: addSuffixToFilePath(this.outputBaseName, '-expected'), contentType: this.mimeType, path: this.snapshotPath }); output.push(`\nExpected: ${colors.yellow(this.snapshotPath)}`); } if (previous !== undefined) { writeFileSync(this.previousPath, previous); - this.testInfo.attachments.push({ name: addSuffixToFilePath(this.snapshotName, '-previous'), contentType: this.mimeType, path: this.previousPath }); + this.testInfo.attachments.push({ name: addSuffixToFilePath(this.outputBaseName, '-previous'), contentType: this.mimeType, path: this.previousPath }); output.push(`Previous: ${colors.yellow(this.previousPath)}`); } if (actual !== undefined) { writeFileSync(this.actualPath, actual); - this.testInfo.attachments.push({ name: addSuffixToFilePath(this.snapshotName, '-actual'), contentType: this.mimeType, path: this.actualPath }); + this.testInfo.attachments.push({ name: addSuffixToFilePath(this.outputBaseName, '-actual'), contentType: this.mimeType, path: this.actualPath }); output.push(`Received: ${colors.yellow(this.actualPath)}`); } if (diff !== undefined) { writeFileSync(this.diffPath, diff); - this.testInfo.attachments.push({ name: addSuffixToFilePath(this.snapshotName, '-diff'), contentType: this.mimeType, path: this.diffPath }); + this.testInfo.attachments.push({ name: addSuffixToFilePath(this.outputBaseName, '-diff'), contentType: this.mimeType, path: this.diffPath }); output.push(` Diff: ${colors.yellow(this.diffPath)}`); } diff --git a/packages/playwright/src/util.ts b/packages/playwright/src/util.ts index d67649276a..d6cabd07c6 100644 --- a/packages/playwright/src/util.ts +++ b/packages/playwright/src/util.ts @@ -185,6 +185,8 @@ export function expectTypes(receiver: any, types: string[], matcherName: string) } } +export const windowsFilesystemFriendlyLength = 60; + export function trimLongString(s: string, length = 100) { if (s.length <= length) return s; diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index e3ec7a46c1..d28f75da84 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -24,7 +24,7 @@ import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutMana import type { RunnableDescription } from './timeoutManager'; import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config'; import type { Location } from '../../types/testReporter'; -import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, serializeError, trimLongString } from '../util'; +import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, serializeError, trimLongString, windowsFilesystemFriendlyLength } from '../util'; import { TestTracing } from './testTracing'; import type { Attachment } from './testTracing'; import type { StackFrame } from '@protocol/channels'; @@ -175,7 +175,7 @@ export class TestInfoImpl implements TestInfo { const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-'); const fullTitleWithoutSpec = this.titlePath.slice(1).join(' '); - let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec)); + let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec), windowsFilesystemFriendlyLength); if (projectInternal.id) testOutputDir += '-' + sanitizeForFilePath(projectInternal.id); if (this.retry) diff --git a/tests/playwright-test/test-output-dir.spec.ts b/tests/playwright-test/test-output-dir.spec.ts index 6d74551303..7768bd5e86 100644 --- a/tests/playwright-test/test-output-dir.spec.ts +++ b/tests/playwright-test/test-output-dir.spec.ts @@ -413,7 +413,7 @@ test('should allow shorten long output dirs characters in the output dir', async `, }); const outputDir = result.outputLines[0]; - expect(outputDir).toBe(path.join(testInfo.outputDir, 'test-results', 'very-deep-and-long-file-name-that-i-want-to-be-99202--keeps-going-and-going-and-we-should-shorten-it')); + expect(outputDir).toBe(path.join(testInfo.outputDir, 'test-results', 'very-deep-and-long-file-na-99202-ng-and-we-should-shorten-it')); }); test('should not mangle double dashes', async ({ runInlineTest }, testInfo) => {