microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
67.02k stars 3.68k forks source link

[Feature]: `snapshotPathTemplate` as optional function #33098

Open ShaMan123 opened 1 month ago

ShaMan123 commented 1 month ago

🚀 Feature Request

I wish to support the following feature in config:

snapshotPathTemplate?: string | ((testInfo: TestInfo) => string)

OR

snapshotPathResolver?: (filename: string, testInfo: TestInfo) => string

snapshotPathResolver should be invoked with the parsed snapshot path (=filename) for further handling, returning the final path to be used.

Currently I am patching playwright to support this. As you xan see it is a very simple change.


diff --git a/node_modules/playwright/.DS_Store b/node_modules/playwright/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/node_modules/playwright/.DS_Store differ
diff --git a/node_modules/playwright/lib/common/config.js b/node_modules/playwright/lib/common/config.js
index 4b21d26..690c308 100644
--- a/node_modules/playwright/lib/common/config.js
+++ b/node_modules/playwright/lib/common/config.js
@@ -158,6 +158,7 @@ class FullProjectInternal {
     const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir);
     const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
     this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
+    this.snapshotPathResolver = takeFirst(projectConfig.snapshotPathResolver, config.snapshotPathResolver, p => p);
     this.project = {
       grep: takeFirst(projectConfig.grep, config.grep, defaultGrep),
       grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null),
diff --git a/node_modules/playwright/lib/worker/testInfo.js b/node_modules/playwright/lib/worker/testInfo.js
index 5ed9668..61bce7f 100644
--- a/node_modules/playwright/lib/worker/testInfo.js
+++ b/node_modules/playwright/lib/worker/testInfo.js
@@ -386,7 +386,8 @@ class TestInfoImpl {
     const parsedRelativeTestFilePath = _path.default.parse(relativeTestFilePath);
     const projectNamePathSegment = (0, _utils.sanitizeForFilePath)(this.project.name);
     const snapshotPath = (this._projectInternal.snapshotPathTemplate || '').replace(/\{(.)?testDir\}/g, '$1' + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, '$1' + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? '$1' + this.snapshotSuffix : '').replace(/\{(.)?testFileDir\}/g, '$1' + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, '$1' + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? '$1' + projectNamePathSegment : '').replace(/\{(.)?testName\}/g, '$1' + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, '$1' + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, '$1' + relativeTestFilePath).replace(/\{(.)?arg\}/g, '$1' + _path.default.join(parsedSubPath.dir, parsedSubPath.name)).replace(/\{(.)?ext\}/g, parsedSubPath.ext ? '$1' + parsedSubPath.ext : '');
-    return _path.default.normalize(_path.default.resolve(this._configInternal.configDir, snapshotPath));
+    const resolvedSnapshotPath = this._projectInternal.snapshotPathResolver ? this._projectInternal.snapshotPathResolver(snapshotPath, this) : snapshotPath;
+    return _path.default.normalize(_path.default.resolve(this._configInternal.configDir, resolvedSnapshotPath));
   }
   skip(...args) {
     this._modifier('skip', args);

Example

It should be used for fine grained runtime control over the snapshot path.

  snapshotPathResolver: (file: string, testInfo: TestInfo) => {
    if (CI) {
      return file;
    }

    const { name, ext, dir } = path.parse(file);
    const resolved = path.join(dir, `${name}.${process.platform}${ext}`);
    if (existsSync(path.resolve(resolved))) {
      console.warn(`Using local snapshot ${resolved}`);
      return resolved;
    }
    return file;
  }

Motivation

In a project I work on some snapshots fail on CI (a very little number) due to OS diffs and we do not want to handle 2 sets of snapshot for dev and for CI so I would like to snapshot locally against a different snapshot

dgozman commented 1 month ago

@ShaMan123 For your specific case, I'd recommend to just ignore these specific snapshots when running locally. If you ever have a lot of snapshots that differ between CI and local, look into ignoreSnapshots option. That said, I will leave this feature request open, just in case it turns out to be a popular ask with various usecases.