2023-02-03 01:46:54 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (c) Microsoft Corporation.
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
import os from 'os';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import { sourceMapSupport } from '../utilsBundle';
|
|
|
|
|
|
|
|
|
|
export type MemoryCache = {
|
|
|
|
|
codePath: string;
|
|
|
|
|
sourceMapPath: string;
|
|
|
|
|
moduleUrl?: string;
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-10 14:13:04 +02:00
|
|
|
const cacheDir = process.env.PWTEST_CACHE_DIR || (() => {
|
|
|
|
|
if (process.platform === 'win32')
|
|
|
|
|
return path.join(os.tmpdir(), `playwright-transform-cache`);
|
|
|
|
|
// Use `geteuid()` instead of more natural `os.userInfo().username`
|
|
|
|
|
// since `os.userInfo()` is not always available.
|
|
|
|
|
// Note: `process.geteuid()` is not available on windows.
|
|
|
|
|
// See https://github.com/microsoft/playwright/issues/22721
|
|
|
|
|
return path.join(os.tmpdir(), `playwright-transform-cache-` + process.geteuid());
|
|
|
|
|
})();
|
2023-02-03 01:46:54 +01:00
|
|
|
|
|
|
|
|
const sourceMaps: Map<string, string> = new Map();
|
|
|
|
|
const memoryCache = new Map<string, MemoryCache>();
|
2023-02-10 17:33:25 +01:00
|
|
|
// Dependencies resolved by the loader.
|
2023-02-07 02:09:16 +01:00
|
|
|
const fileDependencies = new Map<string, Set<string>>();
|
2023-02-10 17:33:25 +01:00
|
|
|
// Dependencies resolved by the external bundler.
|
|
|
|
|
const externalDependencies = new Map<string, Set<string>>();
|
2023-02-03 01:46:54 +01:00
|
|
|
|
|
|
|
|
Error.stackTraceLimit = 200;
|
|
|
|
|
|
|
|
|
|
sourceMapSupport.install({
|
|
|
|
|
environment: 'node',
|
|
|
|
|
handleUncaughtExceptions: false,
|
|
|
|
|
retrieveSourceMap(source) {
|
|
|
|
|
if (!sourceMaps.has(source))
|
|
|
|
|
return null;
|
|
|
|
|
const sourceMapPath = sourceMaps.get(source)!;
|
|
|
|
|
if (!fs.existsSync(sourceMapPath))
|
|
|
|
|
return null;
|
|
|
|
|
return {
|
|
|
|
|
map: JSON.parse(fs.readFileSync(sourceMapPath, 'utf-8')),
|
|
|
|
|
url: source
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function _innerAddToCompilationCache(filename: string, options: { codePath: string, sourceMapPath: string, moduleUrl?: string }) {
|
|
|
|
|
sourceMaps.set(options.moduleUrl || filename, options.sourceMapPath);
|
|
|
|
|
memoryCache.set(filename, options);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 06:09:15 +02:00
|
|
|
export function getFromCompilationCache(filename: string, hash: string, moduleUrl?: string): { cachedCode?: string, addToCache?: (code: string, map?: any) => void } {
|
2023-02-03 01:46:54 +01:00
|
|
|
// First check the memory cache by filename, this cache will always work in the worker,
|
|
|
|
|
// because we just compiled this file in the loader.
|
|
|
|
|
const cache = memoryCache.get(filename);
|
|
|
|
|
if (cache?.codePath)
|
|
|
|
|
return { cachedCode: fs.readFileSync(cache.codePath, 'utf-8') };
|
|
|
|
|
|
|
|
|
|
// Then do the disk cache, this cache works between the Playwright Test runs.
|
2023-05-12 06:09:15 +02:00
|
|
|
const cachePath = calculateCachePath(filename, hash);
|
2023-02-03 01:46:54 +01:00
|
|
|
const codePath = cachePath + '.js';
|
|
|
|
|
const sourceMapPath = cachePath + '.map';
|
|
|
|
|
if (fs.existsSync(codePath)) {
|
|
|
|
|
_innerAddToCompilationCache(filename, { codePath, sourceMapPath, moduleUrl });
|
|
|
|
|
return { cachedCode: fs.readFileSync(codePath, 'utf8') };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
addToCache: (code: string, map: any) => {
|
|
|
|
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
|
|
|
if (map)
|
|
|
|
|
fs.writeFileSync(sourceMapPath, JSON.stringify(map), 'utf8');
|
|
|
|
|
fs.writeFileSync(codePath, code, 'utf8');
|
|
|
|
|
_innerAddToCompilationCache(filename, { codePath, sourceMapPath, moduleUrl });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function serializeCompilationCache(): any {
|
|
|
|
|
return {
|
|
|
|
|
sourceMaps: [...sourceMaps.entries()],
|
|
|
|
|
memoryCache: [...memoryCache.entries()],
|
2023-02-07 02:09:16 +01:00
|
|
|
fileDependencies: [...fileDependencies.entries()].map(([filename, deps]) => ([filename, [...deps]])),
|
2023-02-10 17:33:25 +01:00
|
|
|
externalDependencies: [...externalDependencies.entries()].map(([filename, deps]) => ([filename, [...deps]])),
|
2023-02-03 01:46:54 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-03 18:11:02 +01:00
|
|
|
export function clearCompilationCache() {
|
|
|
|
|
sourceMaps.clear();
|
|
|
|
|
memoryCache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function addToCompilationCache(payload: any) {
|
2023-02-03 01:46:54 +01:00
|
|
|
for (const entry of payload.sourceMaps)
|
|
|
|
|
sourceMaps.set(entry[0], entry[1]);
|
|
|
|
|
for (const entry of payload.memoryCache)
|
|
|
|
|
memoryCache.set(entry[0], entry[1]);
|
2023-02-06 23:52:40 +01:00
|
|
|
for (const entry of payload.fileDependencies)
|
2023-02-07 02:09:16 +01:00
|
|
|
fileDependencies.set(entry[0], new Set(entry[1]));
|
2023-02-10 17:33:25 +01:00
|
|
|
for (const entry of payload.externalDependencies)
|
|
|
|
|
externalDependencies.set(entry[0], new Set(entry[1]));
|
2023-02-03 01:46:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-05-12 06:09:15 +02:00
|
|
|
function calculateCachePath(filePath: string, hash: string): string {
|
2023-02-03 01:46:54 +01:00
|
|
|
const fileName = path.basename(filePath, path.extname(filePath)).replace(/\W/g, '') + '_' + hash;
|
|
|
|
|
return path.join(cacheDir, hash[0] + hash[1], fileName);
|
|
|
|
|
}
|
2023-02-06 23:52:40 +01:00
|
|
|
|
|
|
|
|
// Since ESM and CJS collect dependencies differently,
|
|
|
|
|
// we go via the global state to collect them.
|
|
|
|
|
let depsCollector: Set<string> | undefined;
|
|
|
|
|
|
|
|
|
|
export function startCollectingFileDeps() {
|
|
|
|
|
depsCollector = new Set();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function stopCollectingFileDeps(filename: string) {
|
|
|
|
|
if (!depsCollector)
|
|
|
|
|
return;
|
|
|
|
|
depsCollector.delete(filename);
|
2023-02-07 02:09:16 +01:00
|
|
|
for (const dep of depsCollector) {
|
|
|
|
|
if (belongsToNodeModules(dep))
|
|
|
|
|
depsCollector.delete(dep);
|
|
|
|
|
}
|
|
|
|
|
fileDependencies.set(filename, depsCollector);
|
2023-02-06 23:52:40 +01:00
|
|
|
depsCollector = undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function currentFileDepsCollector(): Set<string> | undefined {
|
|
|
|
|
return depsCollector;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-10 17:33:25 +01:00
|
|
|
export function setExternalDependencies(filename: string, deps: string[]) {
|
|
|
|
|
const depsSet = new Set(deps.filter(dep => !belongsToNodeModules(dep) && dep !== filename));
|
|
|
|
|
externalDependencies.set(filename, depsSet);
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-06 23:52:40 +01:00
|
|
|
export function fileDependenciesForTest() {
|
|
|
|
|
return fileDependencies;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-07 02:09:16 +01:00
|
|
|
export function collectAffectedTestFiles(dependency: string, testFileCollector: Set<string>) {
|
|
|
|
|
testFileCollector.add(dependency);
|
|
|
|
|
for (const [testFile, deps] of fileDependencies) {
|
|
|
|
|
if (deps.has(dependency))
|
|
|
|
|
testFileCollector.add(testFile);
|
|
|
|
|
}
|
2023-02-10 17:33:25 +01:00
|
|
|
for (const [testFile, deps] of externalDependencies) {
|
|
|
|
|
if (deps.has(dependency))
|
|
|
|
|
testFileCollector.add(testFile);
|
|
|
|
|
}
|
2023-02-07 02:09:16 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-03 00:09:50 +01:00
|
|
|
export function dependenciesForTestFile(filename: string): Set<string> {
|
|
|
|
|
return fileDependencies.get(filename) || new Set();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-06 23:52:40 +01:00
|
|
|
// These two are only used in the dev mode, they are specifically excluding
|
|
|
|
|
// files from packages/playwright*. In production mode, node_modules covers
|
|
|
|
|
// that.
|
|
|
|
|
const kPlaywrightInternalPrefix = path.resolve(__dirname, '../../../playwright');
|
|
|
|
|
const kPlaywrightCoveragePrefix = path.resolve(__dirname, '../../../../tests/config/coverage.js');
|
|
|
|
|
|
|
|
|
|
export function belongsToNodeModules(file: string) {
|
|
|
|
|
if (file.includes(`${path.sep}node_modules${path.sep}`))
|
|
|
|
|
return true;
|
2023-05-06 00:12:18 +02:00
|
|
|
if (file.startsWith(kPlaywrightInternalPrefix) && file.endsWith('.js'))
|
2023-02-06 23:52:40 +01:00
|
|
|
return true;
|
2023-05-06 00:12:18 +02:00
|
|
|
if (file.startsWith(kPlaywrightCoveragePrefix) && file.endsWith('.js'))
|
2023-02-06 23:52:40 +01:00
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|