Skip to content

Commit 895a98e

Browse files
feat(selectivity): write testplane test deps
1 parent e72ec24 commit 895a98e

File tree

10 files changed

+339
-41
lines changed

10 files changed

+339
-41
lines changed

src/browser/cdp/selectivity/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { JSSelectivity } from "./js-selectivity";
33
import type { ExistingBrowser } from "../../existing-browser";
44
import { getTestDependenciesWriter } from "./test-dependencies-writer";
55
import type { Test } from "../../../types";
6-
import { transformSourceDependencies } from "./utils";
6+
import { mergeSourceDependencies, transformSourceDependencies } from "./utils";
77
import { getFileHashWriter } from "./file-hash-writer";
88
import { Compression } from "./types";
9+
import { getCollectedTestplaneDependencies } from "./testplane-selectivity";
910

1011
type StopSelectivityFn = (test: Test, shouldWrite: boolean) => Promise<void>;
1112

@@ -63,10 +64,11 @@ export const startSelectivity = async (browser: ExistingBrowser): Promise<StopSe
6364
const compression = browser.config.selectivity.compression;
6465
const testDependencyWriter = getTestDependenciesWriter(testDependenciesPath, compression);
6566
const hashWriter = getFileHashWriter(testDependenciesPath, compression);
66-
const dependencies = transformSourceDependencies(cssDependencies, jsDependencies);
67+
const browserDeps = transformSourceDependencies(cssDependencies, jsDependencies);
68+
const testplaneDeps = transformSourceDependencies([], getCollectedTestplaneDependencies());
6769

68-
hashWriter.add(dependencies);
70+
hashWriter.add(mergeSourceDependencies(browserDeps, testplaneDeps));
6971

70-
await Promise.all([testDependencyWriter.saveFor(test, dependencies), hashWriter.commit()]);
72+
await Promise.all([testDependencyWriter.saveFor(test, browserDeps, testplaneDeps), hashWriter.commit()]);
7173
};
7274
};

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,28 @@ export class TestDependenciesWriter {
4141
this._compression = compression;
4242
}
4343

44-
async saveFor(test: Test, browserDependencies: NormalizedDependencies): Promise<void> {
44+
async saveFor(
45+
test: Test,
46+
browserDeps: NormalizedDependencies,
47+
testplaneDeps: NormalizedDependencies,
48+
): Promise<void> {
4549
if (!this._directoryCreated) {
4650
await fs.ensureDir(this._selectivityTestsPath);
4751
this._directoryCreated = true;
4852
}
4953

5054
const testDepsPath = path.join(this._selectivityTestsPath, `${test.id}.json`);
51-
const testDeps: Record<string, { browser: NormalizedDependencies }> = await readJsonWithCompression(
52-
testDepsPath,
53-
this._compression,
54-
{ defaultValue: {} },
55-
).catch(() => ({}));
55+
const testDeps: Record<string, { browser: NormalizedDependencies; testplane: NormalizedDependencies }> =
56+
await readJsonWithCompression(testDepsPath, this._compression, { defaultValue: {} }).catch(() => ({}));
5657

57-
if (areDepsSame(testDeps[test.browserId]?.browser, browserDependencies)) {
58+
if (
59+
areDepsSame(testDeps[test.browserId]?.browser, browserDeps) &&
60+
areDepsSame(testDeps[test.browserId]?.testplane, testplaneDeps)
61+
) {
5862
return;
5963
}
6064

61-
testDeps[test.browserId] = { browser: browserDependencies };
65+
testDeps[test.browserId] = { browser: browserDeps, testplane: testplaneDeps };
6266

6367
shallowSortObject(testDeps);
6468

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import path from "path";
2+
import { Module as UntypedModule } from "module";
3+
import { AsyncLocalStorage } from "async_hooks";
4+
5+
const TypedModule = UntypedModule as unknown as {
6+
_load: (...args: any) => unknown; // eslint-disable-line @typescript-eslint/no-explicit-any
7+
_resolveFilename: (...args: any) => string; // eslint-disable-line @typescript-eslint/no-explicit-any
8+
};
9+
const testDependenciesStorage = new AsyncLocalStorage<{ jsTestplaneDeps?: Set<string> }>();
10+
11+
let disableCollectingDependenciesCb: (() => void) | null = null;
12+
13+
export const disableCollectingTestplaneDependencies = (): void => {
14+
if (disableCollectingDependenciesCb) {
15+
disableCollectingDependenciesCb();
16+
disableCollectingDependenciesCb = null;
17+
}
18+
};
19+
20+
export const enableCollectingTestplaneDependencies = (): void => {
21+
if (disableCollectingDependenciesCb) {
22+
return;
23+
}
24+
25+
const originalModuleLoad = TypedModule._load;
26+
27+
disableCollectingDependenciesCb = (): void => {
28+
TypedModule._load = originalModuleLoad;
29+
};
30+
31+
TypedModule._load = function (): unknown {
32+
try {
33+
const store = disableCollectingDependenciesCb ? testDependenciesStorage.getStore() : null;
34+
// eslint-disable-next-line prefer-rest-params
35+
const absPath = store ? TypedModule._resolveFilename.apply(this, arguments) : null;
36+
const relPath = absPath && path.isAbsolute(absPath) ? path.relative(process.cwd(), absPath) : null;
37+
38+
if (store && relPath) {
39+
const posixRelPath =
40+
path.sep === path.posix.sep ? relPath : relPath.replaceAll(path.sep, path.posix.sep);
41+
store.jsTestplaneDeps?.add(posixRelPath);
42+
}
43+
} catch {} // eslint-disable-line no-empty
44+
45+
// eslint-disable-next-line prefer-rest-params
46+
return originalModuleLoad.apply(this, arguments);
47+
};
48+
};
49+
50+
export const getCollectedTestplaneDependencies = (): string[] => {
51+
const store = testDependenciesStorage.getStore();
52+
53+
return store && store.jsTestplaneDeps ? Array.from(store.jsTestplaneDeps).sort() : [];
54+
};
55+
56+
export const runWithTestplaneDependenciesCollecting = <T>(fn: () => Promise<T>): Promise<T> => {
57+
enableCollectingTestplaneDependencies();
58+
59+
const store = { jsTestplaneDeps: new Set<string>() };
60+
61+
return testDependenciesStorage.run(store, fn);
62+
};

src/browser/cdp/selectivity/utils.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,57 @@ export const transformSourceDependencies = (
226226
return { css, js, modules };
227227
};
228228

229+
/** Merges two sorted deps array into one with uniq values */
230+
export const mergeSourceDependencies = (
231+
a: NormalizedDependencies,
232+
b: NormalizedDependencies,
233+
): NormalizedDependencies => {
234+
const result: NormalizedDependencies = { css: [], js: [], modules: [] };
235+
236+
for (const depType of Object.keys(result) as Array<keyof NormalizedDependencies>) {
237+
let aInd = 0,
238+
bInd = 0;
239+
240+
while (aInd < a[depType].length || bInd < b[depType].length) {
241+
let compareResult;
242+
243+
if (bInd >= b[depType].length) {
244+
compareResult = -1;
245+
} else if (aInd >= a[depType].length) {
246+
compareResult = 1;
247+
} else {
248+
compareResult = a[depType][aInd].localeCompare(b[depType][bInd]);
249+
}
250+
251+
if (compareResult < 0) {
252+
result[depType].push(a[depType][aInd]);
253+
254+
do {
255+
aInd++;
256+
} while (a[depType][aInd] === a[depType][aInd - 1]);
257+
} else if (compareResult > 0) {
258+
result[depType].push(b[depType][bInd]);
259+
260+
do {
261+
bInd++;
262+
} while (b[depType][bInd] === b[depType][bInd - 1]);
263+
} else {
264+
result[depType].push(a[depType][aInd]);
265+
266+
do {
267+
aInd++;
268+
} while (a[depType][aInd] === a[depType][aInd - 1]);
269+
270+
do {
271+
bInd++;
272+
} while (b[depType][bInd] === b[depType][bInd - 1]);
273+
}
274+
}
275+
}
276+
277+
return result;
278+
};
279+
229280
// Ensures file consistency
230281
export const shallowSortObject = (obj: Record<string, unknown>): void => {
231282
const testBrowsers = Object.keys(obj).sort();

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const fs = require("fs-extra");
44
const path = require("path");
55
const _ = require("lodash");
6-
const { pngValidator: validatePng } = require("png-validator");
76
const { Image } = require("../../../image");
87
const ScreenShooter = require("../../screen-shooter");
98
const temp = require("../../../temp");
@@ -115,7 +114,7 @@ module.exports.default = browser => {
115114
const refBuffer = await fs.readFile(refImg.path);
116115

117116
try {
118-
validatePng(refBuffer);
117+
require("png-validator").pngValidator(refBuffer);
119118
} catch (err) {
120119
await currImgInst.save(currImg.path);
121120

src/worker/runner/index.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const BrowserPool = require("./browser-pool");
77
const { BrowserAgent } = require("./browser-agent");
88
const { CachingTestParser } = require("./caching-test-parser");
99
const { isRunInNodeJsEnv } = require("../../utils/config");
10+
const { runWithTestplaneDependenciesCollecting } = require("../../browser/cdp/selectivity/testplane-selectivity");
1011

1112
module.exports = class Runner extends AsyncEmitter {
1213
static create(config) {
@@ -33,20 +34,27 @@ module.exports = class Runner extends AsyncEmitter {
3334
? await import("./test-runner").then(m => m.default)
3435
: await import("../browser-env/runner/test-runner").then(m => m.TestRunner);
3536

37+
const config = this._config.forBrowser(browserId);
3638
const runner = RunnerClass.create({
3739
file,
38-
config: this._config.forBrowser(browserId),
40+
config,
3941
browserAgent,
4042
attempt,
4143
});
4244

43-
runner.prepareBrowser({ sessionId, sessionCaps, sessionOpts, state });
45+
const prepareParseAndRun = async () => {
46+
runner.prepareBrowser({ sessionId, sessionCaps, sessionOpts, state });
4447

45-
const tests = await this._testParser.parse({ file, browserId });
46-
const test = tests.find(t => t.fullTitle() === fullTitle);
48+
const tests = await this._testParser.parse({ file, browserId });
49+
const test = tests.find(t => t.fullTitle() === fullTitle);
4750

48-
runner.assignTest(test);
51+
runner.assignTest(test);
4952

50-
return runner.run();
53+
return runner.run();
54+
};
55+
56+
return config.selectivity && config.selectivity.enabled && isRunInNodeJsEnv(this._config)
57+
? runWithTestplaneDependenciesCollecting(prepareParseAndRun)
58+
: prepareParseAndRun();
5159
}
5260
};

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

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
5757
js: ["src/app.js"],
5858
modules: ["node_modules/react"],
5959
};
60+
const mockEmptyDependencies = {
61+
css: [],
62+
js: [],
63+
modules: [],
64+
};
6065

6166
it("should create directory on first save", async () => {
6267
const writer = new TestDependenciesWriter("/test/selectivity", "none");
@@ -79,10 +84,10 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
7984
const writer = new TestDependenciesWriter("/test/selectivity", "none");
8085
readJsonWithCompression.resolves({});
8186

82-
await writer.saveFor(mockTest, mockDependencies);
87+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
8388

8489
const expectedPath = "/test/selectivity/tests/test-123.json";
85-
const expectedContent = { chrome: { browser: mockDependencies } };
90+
const expectedContent = { chrome: { browser: mockDependencies, testplane: mockEmptyDependencies } };
8691

8792
assert.calledWith(pathStub.join, "/test/selectivity/tests", "test-123.json");
8893
assert.calledWith(writeJsonWithCompression, expectedPath, expectedContent);
@@ -97,11 +102,11 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
97102

98103
readJsonWithCompression.resolves(existingContent);
99104

100-
await writer.saveFor(mockTest, mockDependencies);
105+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
101106

102107
const expectedContent = {
103108
firefox: { browser: { css: ["old.css"], js: [], modules: [] } },
104-
chrome: { browser: mockDependencies },
109+
chrome: { browser: mockDependencies, testplane: mockEmptyDependencies },
105110
};
106111

107112
assert.calledWith(writeJsonWithCompression, "/test/selectivity/tests/test-123.json", expectedContent);
@@ -110,12 +115,12 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
110115
it("should not save if dependencies are the same", async () => {
111116
const writer = new TestDependenciesWriter("/test/selectivity", "none");
112117
const existingContent = {
113-
chrome: { browser: mockDependencies },
118+
chrome: { browser: mockDependencies, testplane: mockEmptyDependencies },
114119
};
115120

116121
readJsonWithCompression.resolves(existingContent);
117122

118-
await writer.saveFor(mockTest, mockDependencies);
123+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
119124

120125
assert.notCalled(writeJsonWithCompression);
121126
});
@@ -124,9 +129,9 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
124129
const writer = new TestDependenciesWriter("/test/selectivity", "none");
125130
readJsonWithCompression.rejects(new Error("invalid json"));
126131

127-
await writer.saveFor(mockTest, mockDependencies);
132+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
128133

129-
const expectedContent = { chrome: { browser: mockDependencies } };
134+
const expectedContent = { chrome: { browser: mockDependencies, testplane: mockEmptyDependencies } };
130135

131136
assert.calledWith(writeJsonWithCompression, "/test/selectivity/tests/test-123.json", expectedContent);
132137
});
@@ -135,24 +140,24 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
135140
const writer = new TestDependenciesWriter("/test/selectivity", "none");
136141
readJsonWithCompression.resolves({});
137142

138-
await writer.saveFor(mockTest, mockDependencies);
143+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
139144

140-
const expectedContent = { chrome: { browser: mockDependencies } };
145+
const expectedContent = { chrome: { browser: mockDependencies, testplane: mockEmptyDependencies } };
141146

142147
assert.calledWith(writeJsonWithCompression, "/test/selectivity/tests/test-123.json", expectedContent);
143148
});
144149

145150
it("should overwrite existing browser dependencies", async () => {
146151
const writer = new TestDependenciesWriter("/test/selectivity", "none");
147152
const existingContent = {
148-
chrome: { browser: { css: ["old.css"], js: [], modules: [] } },
153+
chrome: { browser: { css: ["old.css"], js: [], modules: [] }, testplane: mockEmptyDependencies },
149154
};
150155

151156
readJsonWithCompression.resolves(existingContent);
152157

153-
await writer.saveFor(mockTest, mockDependencies);
158+
await writer.saveFor(mockTest, mockDependencies, mockEmptyDependencies);
154159

155-
const expectedContent = { chrome: { browser: mockDependencies } };
160+
const expectedContent = { chrome: { browser: mockDependencies, testplane: mockEmptyDependencies } };
156161

157162
assert.calledWith(writeJsonWithCompression, "/test/selectivity/tests/test-123.json", expectedContent);
158163
});
@@ -172,11 +177,11 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
172177
};
173178

174179
const writer = new TestDependenciesWriter("/test/selectivity", "none");
175-
const existingContent = { chrome: { browser: deps1 } };
180+
const existingContent = { chrome: { browser: deps1, testplane: deps1 } };
176181

177182
readJsonWithCompression.resolves(existingContent);
178183

179-
return writer.saveFor({ id: "test", browserId: "chrome" }, deps2).then(() => {
184+
return writer.saveFor({ id: "test", browserId: "chrome" }, deps2, deps2).then(() => {
180185
assert.notCalled(writeJsonWithCompression);
181186
});
182187
});
@@ -194,11 +199,11 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
194199
};
195200

196201
const writer = new TestDependenciesWriter("/test/selectivity", "none");
197-
const existingContent = { chrome: { browser: deps1 } };
202+
const existingContent = { chrome: { browser: deps1, testplane: deps2 } };
198203

199204
readJsonWithCompression.resolves(existingContent);
200205

201-
await writer.saveFor({ id: "test", browserId: "chrome" }, deps2);
206+
await writer.saveFor({ id: "test", browserId: "chrome" }, deps2, deps2);
202207

203208
assert.calledOnce(writeJsonWithCompression);
204209
});
@@ -213,7 +218,7 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
213218
const writer = new TestDependenciesWriter("/test/selectivity", "none");
214219
readJsonWithCompression.resolves({});
215220

216-
await writer.saveFor({ id: "test", browserId: "chrome" }, deps);
221+
await writer.saveFor({ id: "test", browserId: "chrome" }, deps, deps);
217222

218223
assert.calledOnce(writeJsonWithCompression);
219224
});
@@ -231,11 +236,11 @@ describe("CDP/Selectivity/TestDependenciesWriter", () => {
231236
};
232237

233238
const writer = new TestDependenciesWriter("/test/selectivity", "none");
234-
const existingContent = { chrome: { browser: deps1 } };
239+
const existingContent = { chrome: { browser: deps1, testplane: deps1 } };
235240

236241
readJsonWithCompression.resolves(existingContent);
237242

238-
await writer.saveFor({ id: "test", browserId: "chrome" }, deps2);
243+
await writer.saveFor({ id: "test", browserId: "chrome" }, deps2, deps2);
239244

240245
assert.calledOnce(writeJsonWithCompression);
241246
});

0 commit comments

Comments
 (0)