util.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.addSuffixToFilePath = addSuffixToFilePath;
  6. exports.callLogText = void 0;
  7. exports.createFileFiltersFromArguments = createFileFiltersFromArguments;
  8. exports.createFileMatcher = createFileMatcher;
  9. exports.createFileMatcherFromArguments = createFileMatcherFromArguments;
  10. exports.createTitleMatcher = createTitleMatcher;
  11. exports.debugTest = void 0;
  12. exports.errorWithFile = errorWithFile;
  13. exports.expectTypes = expectTypes;
  14. exports.fileIsModule = fileIsModule;
  15. exports.filterStackFile = filterStackFile;
  16. exports.filterStackTrace = filterStackTrace;
  17. exports.filteredStackTrace = filteredStackTrace;
  18. exports.forceRegExp = forceRegExp;
  19. exports.formatLocation = formatLocation;
  20. exports.getContainedPath = getContainedPath;
  21. exports.getPackageJsonPath = getPackageJsonPath;
  22. exports.mergeObjects = mergeObjects;
  23. exports.normalizeAndSaveAttachment = normalizeAndSaveAttachment;
  24. exports.relativeFilePath = relativeFilePath;
  25. exports.resolveImportSpecifierExtension = resolveImportSpecifierExtension;
  26. exports.resolveReporterOutputPath = resolveReporterOutputPath;
  27. exports.serializeError = serializeError;
  28. exports.trimLongString = trimLongString;
  29. var _fs = _interopRequireDefault(require("fs"));
  30. var _utilsBundle = require("playwright-core/lib/utilsBundle");
  31. var _util = _interopRequireDefault(require("util"));
  32. var _path = _interopRequireDefault(require("path"));
  33. var _url = _interopRequireDefault(require("url"));
  34. var _utils = require("playwright-core/lib/utils");
  35. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  36. /**
  37. * Copyright (c) Microsoft Corporation.
  38. *
  39. * Licensed under the Apache License, Version 2.0 (the "License");
  40. * you may not use this file except in compliance with the License.
  41. * You may obtain a copy of the License at
  42. *
  43. * http://www.apache.org/licenses/LICENSE-2.0
  44. *
  45. * Unless required by applicable law or agreed to in writing, software
  46. * distributed under the License is distributed on an "AS IS" BASIS,
  47. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  48. * See the License for the specific language governing permissions and
  49. * limitations under the License.
  50. */
  51. const PLAYWRIGHT_TEST_PATH = _path.default.join(__dirname, '..');
  52. const PLAYWRIGHT_CORE_PATH = _path.default.dirname(require.resolve('playwright-core/package.json'));
  53. function filterStackTrace(e) {
  54. var _e$stack;
  55. if (process.env.PWDEBUGIMPL) return {
  56. message: e.name + ': ' + e.message,
  57. stack: e.stack || ''
  58. };
  59. const stackLines = (0, _utils.stringifyStackFrames)(filteredStackTrace(((_e$stack = e.stack) === null || _e$stack === void 0 ? void 0 : _e$stack.split('\n')) || []));
  60. return {
  61. message: e.name + ': ' + e.message,
  62. stack: `${e.name}: ${e.message}\n${stackLines.join('\n')}`
  63. };
  64. }
  65. function filterStackFile(file) {
  66. if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_TEST_PATH)) return false;
  67. if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_CORE_PATH)) return false;
  68. return true;
  69. }
  70. function filteredStackTrace(rawStack) {
  71. const frames = [];
  72. for (const line of rawStack) {
  73. const frame = (0, _utilsBundle.parseStackTraceLine)(line);
  74. if (!frame || !frame.file) continue;
  75. if (!filterStackFile(frame.file)) continue;
  76. frames.push(frame);
  77. }
  78. return frames;
  79. }
  80. function serializeError(error) {
  81. if (error instanceof Error) return filterStackTrace(error);
  82. return {
  83. value: _util.default.inspect(error)
  84. };
  85. }
  86. function createFileFiltersFromArguments(args) {
  87. return args.map(arg => {
  88. const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
  89. return {
  90. re: forceRegExp(match ? match[1] : arg),
  91. line: match ? parseInt(match[2], 10) : null,
  92. column: match !== null && match !== void 0 && match[3] ? parseInt(match[3], 10) : null
  93. };
  94. });
  95. }
  96. function createFileMatcherFromArguments(args) {
  97. const filters = createFileFiltersFromArguments(args);
  98. return createFileMatcher(filters.map(filter => filter.re || filter.exact || ''));
  99. }
  100. function createFileMatcher(patterns) {
  101. const reList = [];
  102. const filePatterns = [];
  103. for (const pattern of Array.isArray(patterns) ? patterns : [patterns]) {
  104. if ((0, _utils.isRegExp)(pattern)) {
  105. reList.push(pattern);
  106. } else {
  107. if (!pattern.startsWith('**/')) filePatterns.push('**/' + pattern);else filePatterns.push(pattern);
  108. }
  109. }
  110. return filePath => {
  111. for (const re of reList) {
  112. re.lastIndex = 0;
  113. if (re.test(filePath)) return true;
  114. }
  115. // Windows might still receive unix style paths from Cygwin or Git Bash.
  116. // Check against the file url as well.
  117. if (_path.default.sep === '\\') {
  118. const fileURL = _url.default.pathToFileURL(filePath).href;
  119. for (const re of reList) {
  120. re.lastIndex = 0;
  121. if (re.test(fileURL)) return true;
  122. }
  123. }
  124. for (const pattern of filePatterns) {
  125. if ((0, _utilsBundle.minimatch)(filePath, pattern, {
  126. nocase: true,
  127. dot: true
  128. })) return true;
  129. }
  130. return false;
  131. };
  132. }
  133. function createTitleMatcher(patterns) {
  134. const reList = Array.isArray(patterns) ? patterns : [patterns];
  135. return value => {
  136. for (const re of reList) {
  137. re.lastIndex = 0;
  138. if (re.test(value)) return true;
  139. }
  140. return false;
  141. };
  142. }
  143. function mergeObjects(a, b, c) {
  144. const result = {
  145. ...a
  146. };
  147. for (const x of [b, c].filter(Boolean)) {
  148. for (const [name, value] of Object.entries(x)) {
  149. if (!Object.is(value, undefined)) result[name] = value;
  150. }
  151. }
  152. return result;
  153. }
  154. function forceRegExp(pattern) {
  155. const match = pattern.match(/^\/(.*)\/([gi]*)$/);
  156. if (match) return new RegExp(match[1], match[2]);
  157. return new RegExp(pattern, 'gi');
  158. }
  159. function relativeFilePath(file) {
  160. if (!_path.default.isAbsolute(file)) return file;
  161. return _path.default.relative(process.cwd(), file);
  162. }
  163. function formatLocation(location) {
  164. return relativeFilePath(location.file) + ':' + location.line + ':' + location.column;
  165. }
  166. function errorWithFile(file, message) {
  167. return new Error(`${relativeFilePath(file)}: ${message}`);
  168. }
  169. function expectTypes(receiver, types, matcherName) {
  170. if (typeof receiver !== 'object' || !types.includes(receiver.constructor.name)) {
  171. const commaSeparated = types.slice();
  172. const lastType = commaSeparated.pop();
  173. const typesString = commaSeparated.length ? commaSeparated.join(', ') + ' or ' + lastType : lastType;
  174. throw new Error(`${matcherName} can be only used with ${typesString} object${types.length > 1 ? 's' : ''}`);
  175. }
  176. }
  177. function trimLongString(s, length = 100) {
  178. if (s.length <= length) return s;
  179. const hash = (0, _utils.calculateSha1)(s);
  180. const middle = `-${hash.substring(0, 5)}-`;
  181. const start = Math.floor((length - middle.length) / 2);
  182. const end = length - middle.length - start;
  183. return s.substring(0, start) + middle + s.slice(-end);
  184. }
  185. function addSuffixToFilePath(filePath, suffix, customExtension, sanitize = false) {
  186. const dirname = _path.default.dirname(filePath);
  187. const ext = _path.default.extname(filePath);
  188. const name = _path.default.basename(filePath, ext);
  189. const base = _path.default.join(dirname, name);
  190. return (sanitize ? (0, _utils.sanitizeForFilePath)(base) : base) + suffix + (customExtension || ext);
  191. }
  192. /**
  193. * Returns absolute path contained within parent directory.
  194. */
  195. function getContainedPath(parentPath, subPath = '') {
  196. const resolvedPath = _path.default.resolve(parentPath, subPath);
  197. if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + _path.default.sep)) return resolvedPath;
  198. return null;
  199. }
  200. const debugTest = exports.debugTest = (0, _utilsBundle.debug)('pw:test');
  201. const callLogText = exports.callLogText = _utils.formatCallLog;
  202. const folderToPackageJsonPath = new Map();
  203. function getPackageJsonPath(folderPath) {
  204. const cached = folderToPackageJsonPath.get(folderPath);
  205. if (cached !== undefined) return cached;
  206. const packageJsonPath = _path.default.join(folderPath, 'package.json');
  207. if (_fs.default.existsSync(packageJsonPath)) {
  208. folderToPackageJsonPath.set(folderPath, packageJsonPath);
  209. return packageJsonPath;
  210. }
  211. const parentFolder = _path.default.dirname(folderPath);
  212. if (folderPath === parentFolder) {
  213. folderToPackageJsonPath.set(folderPath, '');
  214. return '';
  215. }
  216. const result = getPackageJsonPath(parentFolder);
  217. folderToPackageJsonPath.set(folderPath, result);
  218. return result;
  219. }
  220. function resolveReporterOutputPath(defaultValue, configDir, configValue) {
  221. if (configValue) return _path.default.resolve(configDir, configValue);
  222. let basePath = getPackageJsonPath(configDir);
  223. basePath = basePath ? _path.default.dirname(basePath) : process.cwd();
  224. return _path.default.resolve(basePath, defaultValue);
  225. }
  226. async function normalizeAndSaveAttachment(outputPath, name, options = {}) {
  227. if ((options.path !== undefined ? 1 : 0) + (options.body !== undefined ? 1 : 0) !== 1) throw new Error(`Exactly one of "path" and "body" must be specified`);
  228. if (options.path !== undefined) {
  229. var _options$contentType;
  230. const hash = (0, _utils.calculateSha1)(options.path);
  231. if (!(0, _utils.isString)(name)) throw new Error('"name" should be string.');
  232. const sanitizedNamePrefix = (0, _utils.sanitizeForFilePath)(name) + '-';
  233. const dest = _path.default.join(outputPath, 'attachments', sanitizedNamePrefix + hash + _path.default.extname(options.path));
  234. await _fs.default.promises.mkdir(_path.default.dirname(dest), {
  235. recursive: true
  236. });
  237. await _fs.default.promises.copyFile(options.path, dest);
  238. const contentType = (_options$contentType = options.contentType) !== null && _options$contentType !== void 0 ? _options$contentType : _utilsBundle.mime.getType(_path.default.basename(options.path)) || 'application/octet-stream';
  239. return {
  240. name,
  241. contentType,
  242. path: dest
  243. };
  244. } else {
  245. var _options$contentType2;
  246. const contentType = (_options$contentType2 = options.contentType) !== null && _options$contentType2 !== void 0 ? _options$contentType2 : typeof options.body === 'string' ? 'text/plain' : 'application/octet-stream';
  247. return {
  248. name,
  249. contentType,
  250. body: typeof options.body === 'string' ? Buffer.from(options.body) : options.body
  251. };
  252. }
  253. }
  254. function fileIsModule(file) {
  255. if (file.endsWith('.mjs') || file.endsWith('.mts')) return true;
  256. if (file.endsWith('.cjs') || file.endsWith('.cts')) return false;
  257. const folder = _path.default.dirname(file);
  258. return folderIsModule(folder);
  259. }
  260. function folderIsModule(folder) {
  261. const packageJsonPath = getPackageJsonPath(folder);
  262. if (!packageJsonPath) return false;
  263. // Rely on `require` internal caching logic.
  264. return require(packageJsonPath).type === 'module';
  265. }
  266. // This follows the --moduleResolution=bundler strategy from tsc.
  267. // https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#moduleresolution-bundler
  268. const kExtLookups = new Map([['.js', ['.jsx', '.ts', '.tsx']], ['.jsx', ['.tsx']], ['.cjs', ['.cts']], ['.mjs', ['.mts']], ['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']]]);
  269. function resolveImportSpecifierExtension(resolved) {
  270. if (fileExists(resolved)) return resolved;
  271. for (const [ext, others] of kExtLookups) {
  272. if (!resolved.endsWith(ext)) continue;
  273. for (const other of others) {
  274. const modified = resolved.substring(0, resolved.length - ext.length) + other;
  275. if (fileExists(modified)) return modified;
  276. }
  277. break; // Do not try '' when a more specific extension like '.jsx' matched.
  278. }
  279. if (dirExists(resolved)) {
  280. // If we import a package, let Node.js figure out the correct import based on package.json.
  281. if (fileExists(_path.default.join(resolved, 'package.json'))) return resolved;
  282. // Otherwise, try to find a corresponding index file.
  283. const dirImport = _path.default.join(resolved, 'index');
  284. return resolveImportSpecifierExtension(dirImport);
  285. }
  286. }
  287. function fileExists(resolved) {
  288. var _fs$statSync;
  289. return (_fs$statSync = _fs.default.statSync(resolved, {
  290. throwIfNoEntry: false
  291. })) === null || _fs$statSync === void 0 ? void 0 : _fs$statSync.isFile();
  292. }
  293. function dirExists(resolved) {
  294. var _fs$statSync2;
  295. return (_fs$statSync2 = _fs.default.statSync(resolved, {
  296. throwIfNoEntry: false
  297. })) === null || _fs$statSync2 === void 0 ? void 0 : _fs$statSync2.isDirectory();
  298. }