Skip to content

Commit c3be6ba

Browse files
feat(selectivity): consider png reference deps
1 parent d2b2271 commit c3be6ba

16 files changed

Lines changed: 395 additions & 72 deletions

File tree

src/browser/cdp/selectivity/hash-reader.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,24 @@ export class HashReader {
3535

3636
/** @returns changed deps or null, if nothing changed */
3737
async getTestChangedDeps(testDeps: NormalizedDependencies): Promise<NormalizedDependencies | null> {
38-
const depFileTypes: Array<keyof NormalizedDependencies> = ["css", "js", "modules"] as const;
38+
const depFileTypes: Array<keyof NormalizedDependencies> = ["css", "js", "modules", "png"] as const;
3939
const fileContents = await this._getHashFileContents();
4040

4141
let result: NormalizedDependencies | null = null;
4242

4343
const checkForDepFileType = async (depFileType: keyof NormalizedDependencies): Promise<void> => {
44+
// Old selectivity dependency files did not have "png" property
45+
if (!testDeps[depFileType]) {
46+
return;
47+
}
48+
4449
for (const filePath of testDeps[depFileType]) {
4550
const isChanged = this._fileStateCache.get(filePath);
4651

4752
if (isChanged === false) {
4853
continue;
4954
} else if (isChanged === true) {
50-
result ||= { css: [], js: [], modules: [] };
55+
result ||= { css: [], js: [], modules: [], png: [] };
5156
result[depFileType].push(filePath);
5257
continue;
5358
}
@@ -65,7 +70,7 @@ export class HashReader {
6570
}
6671

6772
if (cachedFileHash !== calculatedFileHash) {
68-
result ||= { css: [], js: [], modules: [] };
73+
result ||= { css: [], js: [], modules: [], png: [] };
6974
result[depFileType].push(filePath);
7075
this._fileStateCache.set(filePath, true);
7176
} else {

src/browser/cdp/selectivity/hash-writer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ export class HashWriter {
5555
}
5656

5757
addTestDependencyHashes(dependencies: NormalizedDependencies): void {
58-
dependencies.css.forEach(dependency => this._addFileDependency(dependency));
59-
dependencies.js.forEach(dependency => this._addFileDependency(dependency));
60-
dependencies.modules.forEach(dependency => this._addModuleDependency(dependency));
58+
dependencies.css?.forEach(dependency => this._addFileDependency(dependency));
59+
dependencies.js?.forEach(dependency => this._addFileDependency(dependency));
60+
dependencies.png?.forEach(dependency => this._addFileDependency(dependency));
61+
dependencies.modules?.forEach(dependency => this._addModuleDependency(dependency));
6162
}
6263

6364
async save(readExisting?: boolean): Promise<void> {

src/browser/cdp/selectivity/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Test, TestDepsContext, TestDepsData } from "../../../types";
88
import { getSelectivityTestsPath, mergeSourceDependencies, transformSourceDependencies } from "./utils";
99
import { getHashWriter } from "./hash-writer";
1010
import { Compression } from "./types";
11-
import { getCollectedTestplaneDependencies } from "./testplane-selectivity";
11+
import { getCollectedTestplaneJsDependencies, getCollectedTestplanePngDependencies } from "./testplane-selectivity";
1212
import { getHashReader } from "./hash-reader";
1313
import type { Config } from "../../../config";
1414
import { MasterEvents } from "../../../events";
@@ -257,10 +257,16 @@ export const startSelectivity = async (browser: ExistingBrowser): Promise<StopSe
257257
: null;
258258

259259
const testDependencyWriter = getTestDependenciesWriter(testDependenciesPath, compression);
260-
const browserDeps = transformSourceDependencies(cssDependencies, jsDependencies, mapBrowserDepsRelativePath);
260+
const browserDeps = transformSourceDependencies(
261+
cssDependencies,
262+
jsDependencies,
263+
null,
264+
mapBrowserDepsRelativePath,
265+
);
261266
const testplaneDeps = transformSourceDependencies(
262267
null,
263-
getCollectedTestplaneDependencies(),
268+
getCollectedTestplaneJsDependencies(),
269+
getCollectedTestplanePngDependencies(),
264270
mapTestplaneDepsRelativePath,
265271
);
266272

src/browser/cdp/selectivity/merge-dumps/merge-tests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const mergeJsonTestContents = (
6565
css: [],
6666
js: [],
6767
modules: [],
68+
png: [],
6869
};
6970

7071
for (const dependencyType in testContent[browserId][dependencyScope]) {

src/browser/cdp/selectivity/test-dependencies-reader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class TestDependenciesReader {
1515

1616
async getFor(test: Test): Promise<NormalizedDependencies> {
1717
const testDeps = await readTestDependencies(this._selectivityTestsPath, test, this._compression);
18-
let result: NormalizedDependencies = { css: [], js: [], modules: [] };
18+
let result: NormalizedDependencies = { css: [], js: [], modules: [], png: [] };
1919

2020
for (const browserId of Object.keys(testDeps)) {
2121
const depTypes = Object.keys(testDeps[browserId]);

src/browser/cdp/selectivity/test-dependencies-writer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { NormalizedDependencies, SelectivityCompressionType } from "./types
77
import { writeJsonWithCompression } from "./json-utils";
88

99
const areDepsSame = (browserDepsA?: NormalizedDependencies, browserDepsB?: NormalizedDependencies): boolean => {
10-
const props: Array<keyof NormalizedDependencies> = ["js", "css", "modules"] as const;
10+
const props: Array<keyof NormalizedDependencies> = ["js", "css", "modules", "png"] as const;
1111

1212
if (!browserDepsA || !browserDepsB) {
1313
return false;

src/browser/cdp/selectivity/testplane-selectivity.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { debugSelectivity } from "./debug";
66

77
// eslint-disable-next-line @typescript-eslint/no-explicit-any
88
const TypedModule = UntypedModule as unknown as { _resolveFilename: (...args: any) => string | void };
9-
const testDependenciesStorage = new AsyncLocalStorage<{ jsTestplaneDeps?: Set<string> }>();
9+
const testDependenciesStorage = new AsyncLocalStorage<{
10+
jsTestplaneDeps?: Set<string>;
11+
pngTestplaneDeps?: Set<string>;
12+
}>();
1013
const testFileDependenciesRamCache = new Map<string, string[]>();
1114
const testFileLocks: Record<string, Promise<void>> = {};
1215

@@ -49,24 +52,42 @@ export const enableCollectingTestplaneDependencies = (): void => {
4952
};
5053
};
5154

52-
export const getCollectedTestplaneDependencies = (): Set<string> | null => {
55+
export const getCollectedTestplaneJsDependencies = (): Set<string> | null => {
5356
const store = testDependenciesStorage.getStore();
5457

5558
return store && store.jsTestplaneDeps ? store.jsTestplaneDeps : null;
5659
};
5760

61+
export const getCollectedTestplanePngDependencies = (): Set<string> | null => {
62+
const store = testDependenciesStorage.getStore();
63+
64+
return store && store.pngTestplaneDeps ? store.pngTestplaneDeps : null;
65+
};
66+
5867
export const runWithTestplaneDependenciesCollecting = <T>(fn: () => Promise<T>): Promise<T> => {
5968
enableCollectingTestplaneDependencies();
6069

61-
const store: { jsTestplaneDeps?: Set<string> } = { jsTestplaneDeps: new Set() };
70+
const store: {
71+
jsTestplaneDeps?: Set<string>;
72+
pngTestplaneDeps?: Set<string>;
73+
} = { jsTestplaneDeps: new Set(), pngTestplaneDeps: new Set() };
6274

6375
return testDependenciesStorage.run(store, fn).finally(() => {
6476
// After "fn" completion, "store" is reachable in CDP ping interval callback, so it never GC-removed
65-
// Thats why we do it manually. Removing "jsTestplaneDeps" is enough, and set remains unchanged, if used
77+
// Thats why we do it manually. It is enough, and set remains unchanged, if used
6678
delete store.jsTestplaneDeps;
79+
delete store.pngTestplaneDeps;
6780
});
6881
};
6982

83+
export const addTestplaneSelectivityPngDependency = (pngPath: string): void => {
84+
const store = testDependenciesStorage.getStore();
85+
86+
if (store && store.pngTestplaneDeps) {
87+
store.pngTestplaneDeps.add(pngPath);
88+
}
89+
};
90+
7091
export const readTestFileWithTestplaneDependenciesCollecting = async <T>(
7192
file: string,
7293
fn: () => Promise<T>,

src/browser/cdp/selectivity/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export interface NormalizedDependencies {
55
js: string[];
66
/** Module names from node_modules (e.g. "react", "@remix-run/router") */
77
modules: string[];
8+
/** Reference paths */
9+
png: string[];
810
}
911

1012
export const Compression = {

src/browser/cdp/selectivity/utils.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,14 @@ const warnUnsupportedProtocol = memoize((protocol: string, dependency: string):
220220
export const transformSourceDependencies = (
221221
cssDependencies: Set<string> | null,
222222
jsDependencies: Set<string> | null,
223+
pngDependencies: Set<string> | null,
223224
mapDependencyPathFn?: null | ((relativePath: string) => string | void),
224225
): NormalizedDependencies => {
225226
const nodeModulesLabel = "node_modules/";
226227
const cssSet: Set<string> = new Set();
227228
const jsSet: Set<string> = new Set();
228229
const modulesSet: Set<string> = new Set();
230+
const pngSet: Set<string> = new Set();
229231

230232
const classifyDependency = (dependency: string, typedResultSet: Set<string>): void => {
231233
dependency = decodeURIComponent(softFileURLToPath(dependency));
@@ -291,12 +293,19 @@ export const transformSourceDependencies = (
291293
}
292294
}
293295

296+
if (pngDependencies) {
297+
for (const pngDependency of pngDependencies.values()) {
298+
classifyDependency(pngDependency, pngSet);
299+
}
300+
}
301+
294302
const cmpStr = (a: string, b: string): number => a.localeCompare(b);
295303

296304
return {
297305
css: Array.from(cssSet).sort(cmpStr),
298306
js: Array.from(jsSet).sort(cmpStr),
299307
modules: Array.from(modulesSet).sort(cmpStr),
308+
png: Array.from(pngSet).sort(cmpStr),
300309
};
301310
};
302311

@@ -305,12 +314,26 @@ export const mergeSourceDependencies = (
305314
a: NormalizedDependencies,
306315
b: NormalizedDependencies,
307316
): NormalizedDependencies => {
308-
const result: NormalizedDependencies = { css: [], js: [], modules: [] };
317+
const result: NormalizedDependencies = { css: [], js: [], modules: [], png: [] };
309318

310319
for (const depType of Object.keys(result) as Array<keyof NormalizedDependencies>) {
311320
let aInd = 0,
312321
bInd = 0;
313322

323+
if (!a[depType]) {
324+
if (!b[depType]) {
325+
continue;
326+
}
327+
328+
result[depType] = b[depType];
329+
330+
continue;
331+
} else if (!b[depType]) {
332+
result[depType] = a[depType];
333+
334+
continue;
335+
}
336+
314337
while (aInd < a[depType].length || bInd < b[depType].length) {
315338
let compareResult;
316339

@@ -392,13 +415,30 @@ export const getTestDependenciesPath = (selectivityTestsPath: string, test: Test
392415
path.join(selectivityTestsPath, `${getTestSelectivityDumpId(test)}.json`);
393416

394417
/** @returns `Promise<Record<BrowserID, Record<DepType, NormalizedDependencies>>>` */
395-
export const readTestDependencies = (
418+
export const readTestDependencies = async (
396419
selectivityTestsPath: string,
397420
test: Test,
398421
compression: SelectivityCompressionType,
399-
): Promise<TestDependenciesFileContents> =>
400-
readJsonWithCompression(getTestDependenciesPath(selectivityTestsPath, test), compression, {
422+
): Promise<TestDependenciesFileContents> => {
423+
const result = (await readJsonWithCompression(getTestDependenciesPath(selectivityTestsPath, test), compression, {
401424
defaultValue: {},
402-
}).catch(() => ({}));
425+
}).catch(() => ({}))) as Record<
426+
string,
427+
Record<string, Partial<NormalizedDependencies> & Pick<NormalizedDependencies, "css" | "js" | "modules" | "png">>
428+
>;
429+
430+
for (const browserId in result) {
431+
for (const depType in result[browserId]) {
432+
const currentDeps = result[browserId][depType];
433+
434+
currentDeps.css ||= [];
435+
currentDeps.js ||= [];
436+
currentDeps.modules ||= [];
437+
currentDeps.png ||= [];
438+
}
439+
}
440+
441+
return result;
442+
};
403443

404444
export const isCachedOnFs = (value: unknown): value is CachedOnFs => value === true;

src/browser/commands/assert-view/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { getCaptureProcessors } = require("./capture-processors");
1010
const RuntimeConfig = require("../../../config/runtime-config");
1111
const AssertViewResults = require("./assert-view-results");
1212
const { BaseStateError } = require("./errors/base-state-error");
13+
const { addTestplaneSelectivityPngDependency } = require("../../cdp/selectivity/testplane-selectivity");
1314

1415
const getIgnoreDiffPixelCountRatio = value => {
1516
const percent = _.isString(value) && value.endsWith("%") ? parseFloat(value.slice(0, -1)) : false;
@@ -102,6 +103,8 @@ module.exports.default = browser => {
102103
return handleNoRefImage(currImg, refImg, state, { emitter }).catch(e => handleCaptureProcessorError(e));
103104
}
104105

106+
addTestplaneSelectivityPngDependency(refImg.path);
107+
105108
const { canHaveCaret, pixelRatio } = page;
106109
const imageCompareOpts = {
107110
tolerance: opts.tolerance,

0 commit comments

Comments
 (0)