mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: refactor toMatchSnapshot once again (#12313)
Keep massaging code in preparation for `toHaveScreenshot`. References #9938
This commit is contained in:
		
							parent
							
								
									e5c9d1e39f
								
							
						
					
					
						commit
						5879c7f362
					
				@ -18,10 +18,10 @@ import { Locator, Page, APIResponse } from 'playwright-core';
 | 
				
			|||||||
import { FrameExpectOptions } from 'playwright-core/lib/client/types';
 | 
					import { FrameExpectOptions } from 'playwright-core/lib/client/types';
 | 
				
			||||||
import { constructURLBasedOnBaseURL } from 'playwright-core/lib/utils/utils';
 | 
					import { constructURLBasedOnBaseURL } from 'playwright-core/lib/utils/utils';
 | 
				
			||||||
import type { Expect } from '../types';
 | 
					import type { Expect } from '../types';
 | 
				
			||||||
import { expectType } from '../util';
 | 
					import { expectType, callLogText } from '../util';
 | 
				
			||||||
import { toBeTruthy } from './toBeTruthy';
 | 
					import { toBeTruthy } from './toBeTruthy';
 | 
				
			||||||
import { toEqual } from './toEqual';
 | 
					import { toEqual } from './toEqual';
 | 
				
			||||||
import { callLogText, toExpectedTextValues, toMatchText } from './toMatchText';
 | 
					import { toExpectedTextValues, toMatchText } from './toMatchText';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface LocatorEx extends Locator {
 | 
					interface LocatorEx extends Locator {
 | 
				
			||||||
  _expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }>;
 | 
					  _expect(expression: string, options: Omit<FrameExpectOptions, 'expectedValue'> & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }>;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,8 +15,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { Expect } from '../types';
 | 
					import type { Expect } from '../types';
 | 
				
			||||||
import { expectType } from '../util';
 | 
					import { expectType, callLogText, currentExpectTimeout } from '../util';
 | 
				
			||||||
import { callLogText, currentExpectTimeout } from './toMatchText';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function toBeTruthy(
 | 
					export async function toBeTruthy(
 | 
				
			||||||
  this: ReturnType<Expect['getState']>,
 | 
					  this: ReturnType<Expect['getState']>,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import type { Expect } from '../types';
 | 
					import type { Expect } from '../types';
 | 
				
			||||||
import { expectType } from '../util';
 | 
					import { expectType } from '../util';
 | 
				
			||||||
import { callLogText, currentExpectTimeout } from './toMatchText';
 | 
					import { callLogText, currentExpectTimeout } from '../util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Omit colon and one or more spaces, so can call getLabelPrinter.
 | 
					// Omit colon and one or more spaces, so can call getLabelPrinter.
 | 
				
			||||||
const EXPECTED_LABEL = 'Expected';
 | 
					const EXPECTED_LABEL = 'Expected';
 | 
				
			||||||
 | 
				
			|||||||
@ -16,8 +16,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import type { Expect } from '../types';
 | 
					import type { Expect } from '../types';
 | 
				
			||||||
import { currentTestInfo } from '../globals';
 | 
					import { currentTestInfo } from '../globals';
 | 
				
			||||||
import { mimeTypeToComparator, ComparatorResult, ImageComparatorOptions } from 'playwright-core/lib/utils/comparators';
 | 
					import { mimeTypeToComparator, ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils/comparators';
 | 
				
			||||||
import { addSuffixToFilePath, serializeError, sanitizeForFilePath, trimLongString } from '../util';
 | 
					import { addSuffixToFilePath, serializeError, sanitizeForFilePath, trimLongString, callLogText } from '../util';
 | 
				
			||||||
import { UpdateSnapshots } from '../types';
 | 
					import { UpdateSnapshots } from '../types';
 | 
				
			||||||
import colors from 'colors/safe';
 | 
					import colors from 'colors/safe';
 | 
				
			||||||
import fs from 'fs';
 | 
					import fs from 'fs';
 | 
				
			||||||
@ -34,68 +34,163 @@ type SyncExpectationResult = {
 | 
				
			|||||||
type NameOrSegments = string | string[];
 | 
					type NameOrSegments = string | string[];
 | 
				
			||||||
const SNAPSHOT_COUNTER = Symbol('noname-snapshot-counter');
 | 
					const SNAPSHOT_COUNTER = Symbol('noname-snapshot-counter');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function parseMatchSnapshotOptions(
 | 
					class SnapshotHelper<T extends ImageComparatorOptions> {
 | 
				
			||||||
  testInfo: TestInfoImpl,
 | 
					  readonly testInfo: TestInfoImpl;
 | 
				
			||||||
  anonymousSnapshotExtension: string,
 | 
					  readonly expectedPath: string;
 | 
				
			||||||
  nameOrOptions: NameOrSegments | { name?: NameOrSegments } & ImageComparatorOptions,
 | 
					  readonly snapshotPath: string;
 | 
				
			||||||
  optOptions: ImageComparatorOptions,
 | 
					  readonly actualPath: string;
 | 
				
			||||||
) {
 | 
					  readonly diffPath: string;
 | 
				
			||||||
  let options: ImageComparatorOptions;
 | 
					  readonly mimeType: string;
 | 
				
			||||||
  let name: NameOrSegments | undefined;
 | 
					  readonly updateSnapshots: UpdateSnapshots;
 | 
				
			||||||
  if (Array.isArray(nameOrOptions) || typeof nameOrOptions === 'string') {
 | 
					  readonly comparatorOptions: T;
 | 
				
			||||||
    name = nameOrOptions;
 | 
					
 | 
				
			||||||
    options = optOptions;
 | 
					  constructor(
 | 
				
			||||||
  } else {
 | 
					    testInfo: TestInfoImpl,
 | 
				
			||||||
    name = nameOrOptions.name;
 | 
					    anonymousSnapshotExtension: string,
 | 
				
			||||||
    options = { ...nameOrOptions };
 | 
					    nameOrOptions: NameOrSegments | { name?: NameOrSegments } & T,
 | 
				
			||||||
    delete (options as any).name;
 | 
					    optOptions: T,
 | 
				
			||||||
  }
 | 
					  ) {
 | 
				
			||||||
  if (!name) {
 | 
					    let options: T;
 | 
				
			||||||
    (testInfo as any)[SNAPSHOT_COUNTER] = ((testInfo as any)[SNAPSHOT_COUNTER] || 0) + 1;
 | 
					    let name: NameOrSegments | undefined;
 | 
				
			||||||
    const fullTitleWithoutSpec = [
 | 
					    if (Array.isArray(nameOrOptions) || typeof nameOrOptions === 'string') {
 | 
				
			||||||
      ...testInfo.titlePath.slice(1),
 | 
					      name = nameOrOptions;
 | 
				
			||||||
      (testInfo as any)[SNAPSHOT_COUNTER],
 | 
					      options = optOptions;
 | 
				
			||||||
    ].join(' ');
 | 
					    } else {
 | 
				
			||||||
    name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension;
 | 
					      name = nameOrOptions.name;
 | 
				
			||||||
 | 
					      options = { ...nameOrOptions };
 | 
				
			||||||
 | 
					      delete (options as any).name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!name) {
 | 
				
			||||||
 | 
					      (testInfo as any)[SNAPSHOT_COUNTER] = ((testInfo as any)[SNAPSHOT_COUNTER] || 0) + 1;
 | 
				
			||||||
 | 
					      const fullTitleWithoutSpec = [
 | 
				
			||||||
 | 
					        ...testInfo.titlePath.slice(1),
 | 
				
			||||||
 | 
					        (testInfo as any)[SNAPSHOT_COUNTER],
 | 
				
			||||||
 | 
					      ].join(' ');
 | 
				
			||||||
 | 
					      name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    options = {
 | 
				
			||||||
 | 
					      ...(testInfo.project.expect?.toMatchSnapshot || {}),
 | 
				
			||||||
 | 
					      ...options,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (options.pixelCount !== undefined && options.pixelCount < 0)
 | 
				
			||||||
 | 
					      throw new Error('`pixelCount` option value must be non-negative integer');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (options.pixelRatio !== undefined && (options.pixelRatio < 0 || options.pixelRatio > 1))
 | 
				
			||||||
 | 
					      throw new Error('`pixelRatio` option value must be between 0 and 1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // sanitizes path if string
 | 
				
			||||||
 | 
					    const pathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, '', undefined, true)];
 | 
				
			||||||
 | 
					    const snapshotPath = testInfo.snapshotPath(...pathSegments);
 | 
				
			||||||
 | 
					    const outputFile = testInfo.outputPath(...pathSegments);
 | 
				
			||||||
 | 
					    const expectedPath = addSuffixToFilePath(outputFile, '-expected');
 | 
				
			||||||
 | 
					    const actualPath = addSuffixToFilePath(outputFile, '-actual');
 | 
				
			||||||
 | 
					    const diffPath = addSuffixToFilePath(outputFile, '-diff');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let updateSnapshots = testInfo.config.updateSnapshots;
 | 
				
			||||||
 | 
					    if (updateSnapshots === 'missing' && testInfo.retry < testInfo.project.retries)
 | 
				
			||||||
 | 
					      updateSnapshots = 'none';
 | 
				
			||||||
 | 
					    const mimeType = mime.getType(path.basename(snapshotPath)) ?? 'application/octet-string';
 | 
				
			||||||
 | 
					    const comparator: Comparator = mimeTypeToComparator[mimeType];
 | 
				
			||||||
 | 
					    if (!comparator)
 | 
				
			||||||
 | 
					      throw new Error('Failed to find comparator with type ' + mimeType + ': ' + snapshotPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.testInfo = testInfo;
 | 
				
			||||||
 | 
					    this.mimeType = mimeType;
 | 
				
			||||||
 | 
					    this.actualPath = actualPath;
 | 
				
			||||||
 | 
					    this.expectedPath = expectedPath;
 | 
				
			||||||
 | 
					    this.diffPath = diffPath;
 | 
				
			||||||
 | 
					    this.snapshotPath = snapshotPath;
 | 
				
			||||||
 | 
					    this.updateSnapshots = updateSnapshots;
 | 
				
			||||||
 | 
					    this.comparatorOptions = options;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  options = {
 | 
					  handleMissingNegated() {
 | 
				
			||||||
    ...(testInfo.project.expect?.toMatchSnapshot || {}),
 | 
					    const isWriteMissingMode = this.updateSnapshots === 'all' || this.updateSnapshots === 'missing';
 | 
				
			||||||
    ...options,
 | 
					    const message = `${this.snapshotPath} is missing in snapshots${isWriteMissingMode ? ', matchers using ".not" won\'t write them automatically.' : '.'}`;
 | 
				
			||||||
  };
 | 
					    return {
 | 
				
			||||||
 | 
					      // NOTE: 'isNot' matcher implies inversed value.
 | 
				
			||||||
 | 
					      pass: true,
 | 
				
			||||||
 | 
					      message: () => message,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (options.pixelCount !== undefined && options.pixelCount < 0)
 | 
					  handleDifferentNegated() {
 | 
				
			||||||
    throw new Error('`pixelCount` option value must be non-negative integer');
 | 
					    // NOTE: 'isNot' matcher implies inversed value.
 | 
				
			||||||
 | 
					    return { pass: false, message: () => '' };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (options.pixelRatio !== undefined && (options.pixelRatio < 0 || options.pixelRatio > 1))
 | 
					  handleMatchingNegated() {
 | 
				
			||||||
    throw new Error('`pixelRatio` option value must be between 0 and 1');
 | 
					    const message = [
 | 
				
			||||||
 | 
					      colors.red('Snapshot comparison failed:'),
 | 
				
			||||||
 | 
					      '',
 | 
				
			||||||
 | 
					      indent('Expected result should be different from the actual one.', '  '),
 | 
				
			||||||
 | 
					    ].join('\n');
 | 
				
			||||||
 | 
					    // NOTE: 'isNot' matcher implies inversed value.
 | 
				
			||||||
 | 
					    return { pass: true, message: () => message };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // sanitizes path if string
 | 
					  handleMissing(actual: Buffer | string) {
 | 
				
			||||||
  const pathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, '', undefined, true)];
 | 
					    const isWriteMissingMode = this.updateSnapshots === 'all' || this.updateSnapshots === 'missing';
 | 
				
			||||||
  const snapshotPath = testInfo.snapshotPath(...pathSegments);
 | 
					    if (isWriteMissingMode) {
 | 
				
			||||||
  const outputFile = testInfo.outputPath(...pathSegments);
 | 
					      writeFileSync(this.snapshotPath, actual);
 | 
				
			||||||
  const expectedPath = addSuffixToFilePath(outputFile, '-expected');
 | 
					      writeFileSync(this.actualPath, actual);
 | 
				
			||||||
  const actualPath = addSuffixToFilePath(outputFile, '-actual');
 | 
					    }
 | 
				
			||||||
  const diffPath = addSuffixToFilePath(outputFile, '-diff');
 | 
					    const message = `${this.snapshotPath} is missing in snapshots${isWriteMissingMode ? ', writing actual.' : '.'}`;
 | 
				
			||||||
 | 
					    if (this.updateSnapshots === 'all') {
 | 
				
			||||||
 | 
					      /* eslint-disable no-console */
 | 
				
			||||||
 | 
					      console.log(message);
 | 
				
			||||||
 | 
					      return { pass: true, message: () => message };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.updateSnapshots === 'missing') {
 | 
				
			||||||
 | 
					      this.testInfo._failWithError(serializeError(new Error(message)), false /* isHardError */);
 | 
				
			||||||
 | 
					      return { pass: true, message: () => '' };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { pass: false, message: () => message };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let updateSnapshots = testInfo.config.updateSnapshots;
 | 
					  handleDifferent(
 | 
				
			||||||
  if (updateSnapshots === 'missing' && testInfo.retry < testInfo.project.retries)
 | 
					    actual: Buffer | string | undefined,
 | 
				
			||||||
    updateSnapshots = 'none';
 | 
					    expected: Buffer | string | undefined,
 | 
				
			||||||
  const mimeType = mime.getType(path.basename(snapshotPath)) ?? 'application/octet-string';
 | 
					    diff: Buffer | string | undefined,
 | 
				
			||||||
  const comparator = mimeTypeToComparator[mimeType];
 | 
					    diffError: string | undefined,
 | 
				
			||||||
  if (!comparator)
 | 
					    log: string[] | undefined,
 | 
				
			||||||
    throw new Error('Failed to find comparator with type ' + mimeType + ': ' + snapshotPath);
 | 
					    title = `Snapshot comparison failed:`) {
 | 
				
			||||||
  return {
 | 
					    const output = [
 | 
				
			||||||
    snapshotPath,
 | 
					      colors.red(title),
 | 
				
			||||||
    hasSnapshotFile: fs.existsSync(snapshotPath),
 | 
					      '',
 | 
				
			||||||
    expectedPath,
 | 
					    ];
 | 
				
			||||||
    actualPath,
 | 
					    if (diffError) {
 | 
				
			||||||
    diffPath,
 | 
					      output.push(...[
 | 
				
			||||||
    comparator,
 | 
					        indent(diffError, '  '),
 | 
				
			||||||
    mimeType,
 | 
					        '',
 | 
				
			||||||
    updateSnapshots,
 | 
					      ]);
 | 
				
			||||||
    options,
 | 
					    }
 | 
				
			||||||
  };
 | 
					    if (log)
 | 
				
			||||||
 | 
					      output.push(callLogText(log));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (expected !== undefined) {
 | 
				
			||||||
 | 
					      writeFileSync(this.expectedPath, expected);
 | 
				
			||||||
 | 
					      this.testInfo.attachments.push({ name: 'expected', contentType: this.mimeType, path: this.expectedPath });
 | 
				
			||||||
 | 
					      output.push(`Expected: ${colors.yellow(this.expectedPath)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (actual !== undefined) {
 | 
				
			||||||
 | 
					      writeFileSync(this.actualPath, actual);
 | 
				
			||||||
 | 
					      this.testInfo.attachments.push({ name: '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: 'diff', contentType: this.mimeType, path: this.diffPath });
 | 
				
			||||||
 | 
					      output.push(`    Diff: ${colors.yellow(this.diffPath)}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { pass: false, message: () => output.join('\n'), };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleMatching() {
 | 
				
			||||||
 | 
					    return { pass: true, message: () => '' };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function toMatchSnapshot(
 | 
					export function toMatchSnapshot(
 | 
				
			||||||
@ -107,124 +202,34 @@ export function toMatchSnapshot(
 | 
				
			|||||||
  const testInfo = currentTestInfo();
 | 
					  const testInfo = currentTestInfo();
 | 
				
			||||||
  if (!testInfo)
 | 
					  if (!testInfo)
 | 
				
			||||||
    throw new Error(`toMatchSnapshot() must be called during the test`);
 | 
					    throw new Error(`toMatchSnapshot() must be called during the test`);
 | 
				
			||||||
  const {
 | 
					  const helper = new SnapshotHelper(testInfo, determineFileExtension(received), nameOrOptions, optOptions);
 | 
				
			||||||
    options,
 | 
					  const comparator: Comparator = mimeTypeToComparator[helper.mimeType];
 | 
				
			||||||
    updateSnapshots,
 | 
					  if (!comparator)
 | 
				
			||||||
    snapshotPath,
 | 
					    throw new Error('Failed to find comparator with type ' + helper.mimeType + ': ' + helper.snapshotPath);
 | 
				
			||||||
    hasSnapshotFile,
 | 
					 | 
				
			||||||
    expectedPath,
 | 
					 | 
				
			||||||
    actualPath,
 | 
					 | 
				
			||||||
    diffPath,
 | 
					 | 
				
			||||||
    mimeType,
 | 
					 | 
				
			||||||
    comparator,
 | 
					 | 
				
			||||||
  } = parseMatchSnapshotOptions(testInfo, determineFileExtension(received), nameOrOptions, optOptions);
 | 
					 | 
				
			||||||
  if (!hasSnapshotFile)
 | 
					 | 
				
			||||||
    return commitMissingSnapshot(testInfo, received, snapshotPath, actualPath, updateSnapshots, this.isNot);
 | 
					 | 
				
			||||||
  const expected = fs.readFileSync(snapshotPath);
 | 
					 | 
				
			||||||
  const result = comparator(received, expected, options);
 | 
					 | 
				
			||||||
  return commitComparatorResult(
 | 
					 | 
				
			||||||
      testInfo,
 | 
					 | 
				
			||||||
      expected,
 | 
					 | 
				
			||||||
      received,
 | 
					 | 
				
			||||||
      result,
 | 
					 | 
				
			||||||
      mimeType,
 | 
					 | 
				
			||||||
      snapshotPath,
 | 
					 | 
				
			||||||
      expectedPath,
 | 
					 | 
				
			||||||
      actualPath,
 | 
					 | 
				
			||||||
      diffPath,
 | 
					 | 
				
			||||||
      updateSnapshots,
 | 
					 | 
				
			||||||
      this.isNot,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function commitMissingSnapshot(
 | 
					  if (this.isNot) {
 | 
				
			||||||
  testInfo: TestInfoImpl,
 | 
					    if (!fs.existsSync(helper.snapshotPath))
 | 
				
			||||||
  actual: Buffer | string,
 | 
					      return helper.handleMissingNegated();
 | 
				
			||||||
  snapshotPath: string,
 | 
					    const isDifferent = !!comparator(received, fs.readFileSync(helper.snapshotPath), helper.comparatorOptions);
 | 
				
			||||||
  actualPath: string,
 | 
					    return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
 | 
				
			||||||
  updateSnapshots: UpdateSnapshots,
 | 
					 | 
				
			||||||
  withNegateComparison: boolean,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const isWriteMissingMode = updateSnapshots === 'all' || updateSnapshots === 'missing';
 | 
					 | 
				
			||||||
  const commonMissingSnapshotMessage = `${snapshotPath} is missing in snapshots`;
 | 
					 | 
				
			||||||
  if (withNegateComparison) {
 | 
					 | 
				
			||||||
    const message = `${commonMissingSnapshotMessage}${isWriteMissingMode ? ', matchers using ".not" won\'t write them automatically.' : '.'}`;
 | 
					 | 
				
			||||||
    return { pass: true , message: () => message };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (isWriteMissingMode) {
 | 
					
 | 
				
			||||||
    writeFileSync(snapshotPath, actual);
 | 
					  if (!fs.existsSync(helper.snapshotPath))
 | 
				
			||||||
    writeFileSync(actualPath, actual);
 | 
					    return helper.handleMissing(received);
 | 
				
			||||||
  }
 | 
					
 | 
				
			||||||
  const message = `${commonMissingSnapshotMessage}${isWriteMissingMode ? ', writing actual.' : '.'}`;
 | 
					  const expected = fs.readFileSync(helper.snapshotPath);
 | 
				
			||||||
  if (updateSnapshots === 'all') {
 | 
					  const result = comparator(received, expected, helper.comparatorOptions);
 | 
				
			||||||
 | 
					  if (!result)
 | 
				
			||||||
 | 
					    return helper.handleMatching();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (helper.updateSnapshots === 'all') {
 | 
				
			||||||
 | 
					    writeFileSync(helper.snapshotPath, received);
 | 
				
			||||||
    /* eslint-disable no-console */
 | 
					    /* eslint-disable no-console */
 | 
				
			||||||
    console.log(message);
 | 
					    console.log(helper.snapshotPath + ' does not match, writing actual.');
 | 
				
			||||||
    return { pass: true, message: () => message };
 | 
					    return { pass: true, message: () => helper.snapshotPath + ' running with --update-snapshots, writing actual.' };
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (updateSnapshots === 'missing') {
 | 
					 | 
				
			||||||
    testInfo._failWithError(serializeError(new Error(message)), false /* isHardError */);
 | 
					 | 
				
			||||||
    return { pass: true, message: () => '' };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return { pass: false, message: () => message };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function commitComparatorResult(
 | 
					 | 
				
			||||||
  testInfo: TestInfoImpl,
 | 
					 | 
				
			||||||
  expected: Buffer | string,
 | 
					 | 
				
			||||||
  actual: Buffer | string,
 | 
					 | 
				
			||||||
  result: ComparatorResult,
 | 
					 | 
				
			||||||
  mimeType: string,
 | 
					 | 
				
			||||||
  snapshotPath: string,
 | 
					 | 
				
			||||||
  expectedPath: string,
 | 
					 | 
				
			||||||
  actualPath: string,
 | 
					 | 
				
			||||||
  diffPath: string,
 | 
					 | 
				
			||||||
  updateSnapshots: UpdateSnapshots,
 | 
					 | 
				
			||||||
  withNegateComparison: boolean,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  if (!result) {
 | 
					 | 
				
			||||||
    const message = withNegateComparison ? [
 | 
					 | 
				
			||||||
      colors.red('Snapshot comparison failed:'),
 | 
					 | 
				
			||||||
      '',
 | 
					 | 
				
			||||||
      indent('Expected result should be different from the actual one.', '  '),
 | 
					 | 
				
			||||||
    ].join('\n') : '';
 | 
					 | 
				
			||||||
    return { pass: true, message: () => message };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (withNegateComparison)
 | 
					  return helper.handleDifferent(received, expected, result.diff, result.errorMessage, undefined);
 | 
				
			||||||
    return { pass: false, message: () => '' };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (updateSnapshots === 'all') {
 | 
					 | 
				
			||||||
    writeFileSync(snapshotPath, actual);
 | 
					 | 
				
			||||||
    /* eslint-disable no-console */
 | 
					 | 
				
			||||||
    console.log(snapshotPath + ' does not match, writing actual.');
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      pass: true,
 | 
					 | 
				
			||||||
      message: () => snapshotPath + ' running with --update-snapshots, writing actual.'
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  writeAttachment(testInfo, 'expected', mimeType, expectedPath, expected);
 | 
					 | 
				
			||||||
  writeAttachment(testInfo, 'actual', mimeType, actualPath, actual);
 | 
					 | 
				
			||||||
  if (result.diff)
 | 
					 | 
				
			||||||
    writeAttachment(testInfo, 'diff', mimeType, diffPath, result.diff);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const output = [
 | 
					 | 
				
			||||||
    colors.red(`Snapshot comparison failed:`),
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
  if (result.errorMessage) {
 | 
					 | 
				
			||||||
    output.push('');
 | 
					 | 
				
			||||||
    output.push(indent(result.errorMessage, '  '));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  output.push('');
 | 
					 | 
				
			||||||
  output.push(`Expected: ${colors.yellow(expectedPath)}`);
 | 
					 | 
				
			||||||
  output.push(`Received: ${colors.yellow(actualPath)}`);
 | 
					 | 
				
			||||||
  if (result.diff)
 | 
					 | 
				
			||||||
    output.push(`    Diff: ${colors.yellow(diffPath)}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    pass: false,
 | 
					 | 
				
			||||||
    message: () => output.join('\n'),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function writeFileSync(aPath: string, content: Buffer | string) {
 | 
					function writeFileSync(aPath: string, content: Buffer | string) {
 | 
				
			||||||
@ -232,11 +237,6 @@ function writeFileSync(aPath: string, content: Buffer | string) {
 | 
				
			|||||||
  fs.writeFileSync(aPath, content);
 | 
					  fs.writeFileSync(aPath, content);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function writeAttachment(testInfo: TestInfoImpl, name: string, contentType: string, aPath: string, body: Buffer | string) {
 | 
					 | 
				
			||||||
  writeFileSync(aPath, body);
 | 
					 | 
				
			||||||
  testInfo.attachments.push({ name, contentType, path: aPath });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function indent(lines: string, tab: string) {
 | 
					function indent(lines: string, tab: string) {
 | 
				
			||||||
  return lines.replace(/^(?=.+$)/gm, tab);
 | 
					  return lines.replace(/^(?=.+$)/gm, tab);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,12 +15,10 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import colors from 'colors/safe';
 | 
					 | 
				
			||||||
import type { ExpectedTextValue } from 'playwright-core/lib/protocol/channels';
 | 
					import type { ExpectedTextValue } from 'playwright-core/lib/protocol/channels';
 | 
				
			||||||
import { isRegExp, isString } from 'playwright-core/lib/utils/utils';
 | 
					import { isRegExp, isString } from 'playwright-core/lib/utils/utils';
 | 
				
			||||||
import { currentTestInfo } from '../globals';
 | 
					 | 
				
			||||||
import type { Expect } from '../types';
 | 
					import type { Expect } from '../types';
 | 
				
			||||||
import { expectType } from '../util';
 | 
					import { expectType, callLogText, currentExpectTimeout } from '../util';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  printReceivedStringContainExpectedResult,
 | 
					  printReceivedStringContainExpectedResult,
 | 
				
			||||||
  printReceivedStringContainExpectedSubstring
 | 
					  printReceivedStringContainExpectedSubstring
 | 
				
			||||||
@ -111,22 +109,3 @@ export function toExpectedTextValues(items: (string | RegExp)[], options: { matc
 | 
				
			|||||||
    normalizeWhiteSpace: options.normalizeWhiteSpace,
 | 
					    normalizeWhiteSpace: options.normalizeWhiteSpace,
 | 
				
			||||||
  }));
 | 
					  }));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function callLogText(log: string[] | undefined): string {
 | 
					 | 
				
			||||||
  if (!log)
 | 
					 | 
				
			||||||
    return '';
 | 
					 | 
				
			||||||
  return `
 | 
					 | 
				
			||||||
Call log:
 | 
					 | 
				
			||||||
  ${colors.dim('- ' + (log || []).join('\n  - '))}
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function currentExpectTimeout(options: { timeout?: number }) {
 | 
					 | 
				
			||||||
  const testInfo = currentTestInfo();
 | 
					 | 
				
			||||||
  if (options.timeout !== undefined)
 | 
					 | 
				
			||||||
    return options.timeout;
 | 
					 | 
				
			||||||
  let defaultExpectTimeout = testInfo?.project.expect?.timeout;
 | 
					 | 
				
			||||||
  if (typeof defaultExpectTimeout === 'undefined')
 | 
					 | 
				
			||||||
    defaultExpectTimeout = 5000;
 | 
					 | 
				
			||||||
  return defaultExpectTimeout;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -17,11 +17,13 @@
 | 
				
			|||||||
import util from 'util';
 | 
					import util from 'util';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
import url from 'url';
 | 
					import url from 'url';
 | 
				
			||||||
 | 
					import colors from 'colors/safe';
 | 
				
			||||||
import type { TestError, Location } from './types';
 | 
					import type { TestError, Location } from './types';
 | 
				
			||||||
import { default as minimatch } from 'minimatch';
 | 
					import { default as minimatch } from 'minimatch';
 | 
				
			||||||
import debug from 'debug';
 | 
					import debug from 'debug';
 | 
				
			||||||
import { calculateSha1, isRegExp } from 'playwright-core/lib/utils/utils';
 | 
					import { calculateSha1, isRegExp } from 'playwright-core/lib/utils/utils';
 | 
				
			||||||
import { isInternalFileName } from 'playwright-core/lib/utils/stackTrace';
 | 
					import { isInternalFileName } from 'playwright-core/lib/utils/stackTrace';
 | 
				
			||||||
 | 
					import { currentTestInfo } from './globals';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core'));
 | 
					const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core'));
 | 
				
			||||||
const EXPECT_PATH = path.dirname(require.resolve('expect'));
 | 
					const EXPECT_PATH = path.dirname(require.resolve('expect'));
 | 
				
			||||||
@ -205,3 +207,23 @@ export function getContainedPath(parentPath: string, subPath: string = ''): stri
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const debugTest = debug('pw:test');
 | 
					export const debugTest = debug('pw:test');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function callLogText(log: string[] | undefined): string {
 | 
				
			||||||
 | 
					  if (!log)
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  return `
 | 
				
			||||||
 | 
					Call log:
 | 
				
			||||||
 | 
					  ${colors.dim('- ' + (log || []).join('\n  - '))}
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function currentExpectTimeout(options: { timeout?: number }) {
 | 
				
			||||||
 | 
					  const testInfo = currentTestInfo();
 | 
				
			||||||
 | 
					  if (options.timeout !== undefined)
 | 
				
			||||||
 | 
					    return options.timeout;
 | 
				
			||||||
 | 
					  let defaultExpectTimeout = testInfo?.project.expect?.timeout;
 | 
				
			||||||
 | 
					  if (typeof defaultExpectTimeout === 'undefined')
 | 
				
			||||||
 | 
					    defaultExpectTimeout = 5000;
 | 
				
			||||||
 | 
					  return defaultExpectTimeout;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,8 +17,7 @@
 | 
				
			|||||||
import colors from 'colors/safe';
 | 
					import colors from 'colors/safe';
 | 
				
			||||||
import * as fs from 'fs';
 | 
					import * as fs from 'fs';
 | 
				
			||||||
import * as path from 'path';
 | 
					import * as path from 'path';
 | 
				
			||||||
import { PNG } from 'pngjs';
 | 
					import { test, expect, stripAnsi, createWhiteImage, paintBlackPixels } from './playwright-test-fixtures';
 | 
				
			||||||
import { test, expect, stripAnsi } from './playwright-test-fixtures';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const files = {
 | 
					const files = {
 | 
				
			||||||
  'helper.ts': `
 | 
					  'helper.ts': `
 | 
				
			||||||
@ -167,21 +166,6 @@ test("doesn\'t create comparison artifacts in an output folder for passed negate
 | 
				
			|||||||
  expect(fs.existsSync(actualSnapshotArtifactPath)).toBe(false);
 | 
					  expect(fs.existsSync(actualSnapshotArtifactPath)).toBe(false);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('should pass on different snapshots with negate matcher', async ({ runInlineTest }) => {
 | 
					 | 
				
			||||||
  const result = await runInlineTest({
 | 
					 | 
				
			||||||
    ...files,
 | 
					 | 
				
			||||||
    'a.spec.js-snapshots/snapshot.txt': `Hello world`,
 | 
					 | 
				
			||||||
    'a.spec.js': `
 | 
					 | 
				
			||||||
      const { test } = require('./helper');
 | 
					 | 
				
			||||||
      test('is a test', ({}) => {
 | 
					 | 
				
			||||||
        expect('Hello world updated').not.toMatchSnapshot('snapshot.txt');
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    `
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  expect(result.exitCode).toBe(0);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
test('should fail on same snapshots with negate matcher', async ({ runInlineTest }) => {
 | 
					test('should fail on same snapshots with negate matcher', async ({ runInlineTest }) => {
 | 
				
			||||||
  const result = await runInlineTest({
 | 
					  const result = await runInlineTest({
 | 
				
			||||||
    ...files,
 | 
					    ...files,
 | 
				
			||||||
@ -443,7 +427,8 @@ test('should throw for invalid pixelRatio values', async ({ runInlineTest }) =>
 | 
				
			|||||||
test('should respect pixelCount option', async ({ runInlineTest }) => {
 | 
					test('should respect pixelCount option', async ({ runInlineTest }) => {
 | 
				
			||||||
  const width = 20, height = 20;
 | 
					  const width = 20, height = 20;
 | 
				
			||||||
  const BAD_PIXELS = 120;
 | 
					  const BAD_PIXELS = 120;
 | 
				
			||||||
  const [image1, image2] = createImagesWithDifferentPixels(width, height, BAD_PIXELS);
 | 
					  const image1 = createWhiteImage(width, height);
 | 
				
			||||||
 | 
					  const image2 = paintBlackPixels(image1, BAD_PIXELS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  expect((await runInlineTest({
 | 
					  expect((await runInlineTest({
 | 
				
			||||||
    ...files,
 | 
					    ...files,
 | 
				
			||||||
@ -488,8 +473,10 @@ test('should respect pixelCount option', async ({ runInlineTest }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
test('should respect pixelRatio option', async ({ runInlineTest }) => {
 | 
					test('should respect pixelRatio option', async ({ runInlineTest }) => {
 | 
				
			||||||
  const width = 20, height = 20;
 | 
					  const width = 20, height = 20;
 | 
				
			||||||
  const BAD_PERCENT = 0.25;
 | 
					  const BAD_RATIO = 0.25;
 | 
				
			||||||
  const [image1, image2] = createImagesWithDifferentPixels(width, height, width * height * BAD_PERCENT);
 | 
					  const BAD_PIXELS = Math.floor(width * height * BAD_RATIO);
 | 
				
			||||||
 | 
					  const image1 = createWhiteImage(width, height);
 | 
				
			||||||
 | 
					  const image2 = paintBlackPixels(image1, BAD_PIXELS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  expect((await runInlineTest({
 | 
					  expect((await runInlineTest({
 | 
				
			||||||
    ...files,
 | 
					    ...files,
 | 
				
			||||||
@ -509,7 +496,7 @@ test('should respect pixelRatio option', async ({ runInlineTest }) => {
 | 
				
			|||||||
      const { test } = require('./helper');
 | 
					      const { test } = require('./helper');
 | 
				
			||||||
      test('is a test', ({}) => {
 | 
					      test('is a test', ({}) => {
 | 
				
			||||||
        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
					        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
				
			||||||
          pixelRatio: ${BAD_PERCENT}
 | 
					          pixelRatio: ${BAD_RATIO}
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
@ -519,7 +506,7 @@ test('should respect pixelRatio option', async ({ runInlineTest }) => {
 | 
				
			|||||||
    ...files,
 | 
					    ...files,
 | 
				
			||||||
    'playwright.config.ts': `
 | 
					    'playwright.config.ts': `
 | 
				
			||||||
      module.exports = { projects: [
 | 
					      module.exports = { projects: [
 | 
				
			||||||
        { expect: { toMatchSnapshot: { pixelRatio: ${BAD_PERCENT} } } },
 | 
					        { expect: { toMatchSnapshot: { pixelRatio: ${BAD_RATIO} } } },
 | 
				
			||||||
      ]};
 | 
					      ]};
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    'a.spec.js-snapshots/snapshot.png': image1,
 | 
					    'a.spec.js-snapshots/snapshot.png': image1,
 | 
				
			||||||
@ -534,9 +521,10 @@ test('should respect pixelRatio option', async ({ runInlineTest }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
test('should satisfy both pixelRatio and pixelCount', async ({ runInlineTest }) => {
 | 
					test('should satisfy both pixelRatio and pixelCount', async ({ runInlineTest }) => {
 | 
				
			||||||
  const width = 20, height = 20;
 | 
					  const width = 20, height = 20;
 | 
				
			||||||
  const BAD_PERCENT = 0.25;
 | 
					  const BAD_RATIO = 0.25;
 | 
				
			||||||
  const BAD_COUNT = Math.floor(width * height * BAD_PERCENT);
 | 
					  const BAD_COUNT = Math.floor(width * height * BAD_RATIO);
 | 
				
			||||||
  const [image1, image2] = createImagesWithDifferentPixels(width, height, BAD_COUNT);
 | 
					  const image1 = createWhiteImage(width, height);
 | 
				
			||||||
 | 
					  const image2 = paintBlackPixels(image1, BAD_COUNT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  expect((await runInlineTest({
 | 
					  expect((await runInlineTest({
 | 
				
			||||||
    ...files,
 | 
					    ...files,
 | 
				
			||||||
@ -557,7 +545,7 @@ test('should satisfy both pixelRatio and pixelCount', async ({ runInlineTest })
 | 
				
			|||||||
      test('is a test', ({}) => {
 | 
					      test('is a test', ({}) => {
 | 
				
			||||||
        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
					        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
				
			||||||
          pixelCount: ${Math.floor(BAD_COUNT / 2)},
 | 
					          pixelCount: ${Math.floor(BAD_COUNT / 2)},
 | 
				
			||||||
          pixelRatio: ${BAD_PERCENT},
 | 
					          pixelRatio: ${BAD_RATIO},
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
@ -571,7 +559,7 @@ test('should satisfy both pixelRatio and pixelCount', async ({ runInlineTest })
 | 
				
			|||||||
      test('is a test', ({}) => {
 | 
					      test('is a test', ({}) => {
 | 
				
			||||||
        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
					        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
				
			||||||
          pixelCount: ${BAD_COUNT},
 | 
					          pixelCount: ${BAD_COUNT},
 | 
				
			||||||
          pixelRatio: ${BAD_PERCENT / 2},
 | 
					          pixelRatio: ${BAD_RATIO / 2},
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
@ -585,7 +573,7 @@ test('should satisfy both pixelRatio and pixelCount', async ({ runInlineTest })
 | 
				
			|||||||
      test('is a test', ({}) => {
 | 
					      test('is a test', ({}) => {
 | 
				
			||||||
        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
					        expect(Buffer.from('${image2.toString('base64')}', 'base64')).toMatchSnapshot('snapshot.png', {
 | 
				
			||||||
          pixelCount: ${BAD_COUNT},
 | 
					          pixelCount: ${BAD_COUNT},
 | 
				
			||||||
          pixelRatio: ${BAD_PERCENT},
 | 
					          pixelRatio: ${BAD_RATIO},
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
@ -635,7 +623,6 @@ test('should compare different PNG images', async ({ runInlineTest }, testInfo)
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('should respect threshold', async ({ runInlineTest }) => {
 | 
					test('should respect threshold', async ({ runInlineTest }) => {
 | 
				
			||||||
  test.skip(!!process.env.PW_USE_BLINK_DIFF);
 | 
					 | 
				
			||||||
  const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
 | 
					  const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
 | 
				
			||||||
  const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
 | 
					  const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
 | 
				
			||||||
  const result = await runInlineTest({
 | 
					  const result = await runInlineTest({
 | 
				
			||||||
@ -656,7 +643,6 @@ test('should respect threshold', async ({ runInlineTest }) => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('should respect project threshold', async ({ runInlineTest }) => {
 | 
					test('should respect project threshold', async ({ runInlineTest }) => {
 | 
				
			||||||
  test.skip(!!process.env.PW_USE_BLINK_DIFF);
 | 
					 | 
				
			||||||
  const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
 | 
					  const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
 | 
				
			||||||
  const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
 | 
					  const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
 | 
				
			||||||
  const result = await runInlineTest({
 | 
					  const result = await runInlineTest({
 | 
				
			||||||
@ -1004,21 +990,3 @@ test('should allow comparing text with text without file extension', async ({ ru
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  expect(result.exitCode).toBe(0);
 | 
					  expect(result.exitCode).toBe(0);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
function createImagesWithDifferentPixels(width: number, height: number, differentPixels: number): [Buffer, Buffer] {
 | 
					 | 
				
			||||||
  const image1 = new PNG({ width, height });
 | 
					 | 
				
			||||||
  const image2 = new PNG({ width, height });
 | 
					 | 
				
			||||||
  // Make both images red.
 | 
					 | 
				
			||||||
  for (let i = 0; i < width * height; ++i) {
 | 
					 | 
				
			||||||
    image1.data[i * 4] = 255; // red
 | 
					 | 
				
			||||||
    image1.data[i * 4 + 3] = 255; // opacity
 | 
					 | 
				
			||||||
    image2.data[i * 4] = 255; // red
 | 
					 | 
				
			||||||
    image2.data[i * 4 + 3] = 255; // opacity
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // Color some pixels blue.
 | 
					 | 
				
			||||||
  for (let i = 0; i < differentPixels; ++i) {
 | 
					 | 
				
			||||||
    image1.data[i * 4] = 0; // red
 | 
					 | 
				
			||||||
    image1.data[i * 4 + 2] = 255; // blue
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return [PNG.sync.write(image1), PNG.sync.write(image2)];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ import type { JSONReport, JSONReportSuite } from '@playwright/test/src/reporters
 | 
				
			|||||||
import * as fs from 'fs';
 | 
					import * as fs from 'fs';
 | 
				
			||||||
import * as os from 'os';
 | 
					import * as os from 'os';
 | 
				
			||||||
import * as path from 'path';
 | 
					import * as path from 'path';
 | 
				
			||||||
 | 
					import { PNG } from 'pngjs';
 | 
				
			||||||
import rimraf from 'rimraf';
 | 
					import rimraf from 'rimraf';
 | 
				
			||||||
import { promisify } from 'util';
 | 
					import { promisify } from 'util';
 | 
				
			||||||
import { CommonFixtures, commonFixtures } from '../config/commonFixtures';
 | 
					import { CommonFixtures, commonFixtures } from '../config/commonFixtures';
 | 
				
			||||||
@ -265,3 +266,28 @@ export function countTimes(s: string, sub: string): number {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return result;
 | 
					  return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createImage(width: number, height: number, r: number = 0, g: number = 0, b: number = 0): Buffer {
 | 
				
			||||||
 | 
					  const image = new PNG({ width, height });
 | 
				
			||||||
 | 
					  // Make both images red.
 | 
				
			||||||
 | 
					  for (let i = 0; i < width * height; ++i) {
 | 
				
			||||||
 | 
					    image.data[i * 4 + 0] = r;
 | 
				
			||||||
 | 
					    image.data[i * 4 + 1] = g;
 | 
				
			||||||
 | 
					    image.data[i * 4 + 2] = b;
 | 
				
			||||||
 | 
					    image.data[i * 4 + 3] = 255;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return PNG.sync.write(image);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createWhiteImage(width: number, height: number) {
 | 
				
			||||||
 | 
					  return createImage(width, height, 255, 255, 255);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function paintBlackPixels(image: Buffer, blackPixelsCount: number): Buffer {
 | 
				
			||||||
 | 
					  image = PNG.sync.read(image);
 | 
				
			||||||
 | 
					  for (let i = 0; i < blackPixelsCount; ++i) {
 | 
				
			||||||
 | 
					    for (let j = 0; j < 3; ++j)
 | 
				
			||||||
 | 
					      image.data[i * 4 + j] = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return PNG.sync.write(image);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user