"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addSuffixToFilePath = addSuffixToFilePath; exports.callLogText = void 0; exports.createFileFiltersFromArguments = createFileFiltersFromArguments; exports.createFileMatcher = createFileMatcher; exports.createFileMatcherFromArguments = createFileMatcherFromArguments; exports.createTitleMatcher = createTitleMatcher; exports.debugTest = void 0; exports.errorWithFile = errorWithFile; exports.expectTypes = expectTypes; exports.fileIsModule = fileIsModule; exports.filterStackFile = filterStackFile; exports.filterStackTrace = filterStackTrace; exports.filteredStackTrace = filteredStackTrace; exports.forceRegExp = forceRegExp; exports.formatLocation = formatLocation; exports.getContainedPath = getContainedPath; exports.getPackageJsonPath = getPackageJsonPath; exports.mergeObjects = mergeObjects; exports.normalizeAndSaveAttachment = normalizeAndSaveAttachment; exports.relativeFilePath = relativeFilePath; exports.removeDirAndLogToConsole = removeDirAndLogToConsole; exports.resolveImportSpecifierAfterMapping = resolveImportSpecifierAfterMapping; exports.resolveReporterOutputPath = resolveReporterOutputPath; exports.sanitizeFilePathBeforeExtension = sanitizeFilePathBeforeExtension; exports.serializeError = serializeError; exports.trimLongString = trimLongString; exports.windowsFilesystemFriendlyLength = void 0; var _fs = _interopRequireDefault(require("fs")); var _util = _interopRequireDefault(require("util")); var _path = _interopRequireDefault(require("path")); var _url = _interopRequireDefault(require("url")); var _utilsBundle = require("playwright-core/lib/utilsBundle"); var _utils = require("playwright-core/lib/utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * 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. */ const PLAYWRIGHT_TEST_PATH = _path.default.join(__dirname, '..'); const PLAYWRIGHT_CORE_PATH = _path.default.dirname(require.resolve('playwright-core/package.json')); function filterStackTrace(e) { var _e$stack; const name = e.name ? e.name + ': ' : ''; const cause = e.cause instanceof Error ? filterStackTrace(e.cause) : undefined; if (process.env.PWDEBUGIMPL) return { message: name + e.message, stack: e.stack || '', cause }; const stackLines = (0, _utils.stringifyStackFrames)(filteredStackTrace(((_e$stack = e.stack) === null || _e$stack === void 0 ? void 0 : _e$stack.split('\n')) || [])); return { message: name + e.message, stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`, cause }; } function filterStackFile(file) { if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_TEST_PATH)) return false; if (!process.env.PWDEBUGIMPL && file.startsWith(PLAYWRIGHT_CORE_PATH)) return false; return true; } function filteredStackTrace(rawStack) { const frames = []; for (const line of rawStack) { const frame = (0, _utilsBundle.parseStackTraceLine)(line); if (!frame || !frame.file) continue; if (!filterStackFile(frame.file)) continue; frames.push(frame); } return frames; } function serializeError(error) { if (error instanceof Error) return filterStackTrace(error); return { value: _util.default.inspect(error) }; } function createFileFiltersFromArguments(args) { return args.map(arg => { const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg); return { re: forceRegExp(match ? match[1] : arg), line: match ? parseInt(match[2], 10) : null, column: match !== null && match !== void 0 && match[3] ? parseInt(match[3], 10) : null }; }); } function createFileMatcherFromArguments(args) { const filters = createFileFiltersFromArguments(args); return createFileMatcher(filters.map(filter => filter.re || filter.exact || '')); } function createFileMatcher(patterns) { const reList = []; const filePatterns = []; for (const pattern of Array.isArray(patterns) ? patterns : [patterns]) { if ((0, _utils.isRegExp)(pattern)) { reList.push(pattern); } else { if (!pattern.startsWith('**/')) filePatterns.push('**/' + pattern);else filePatterns.push(pattern); } } return filePath => { for (const re of reList) { re.lastIndex = 0; if (re.test(filePath)) return true; } // Windows might still receive unix style paths from Cygwin or Git Bash. // Check against the file url as well. if (_path.default.sep === '\\') { const fileURL = _url.default.pathToFileURL(filePath).href; for (const re of reList) { re.lastIndex = 0; if (re.test(fileURL)) return true; } } for (const pattern of filePatterns) { if ((0, _utilsBundle.minimatch)(filePath, pattern, { nocase: true, dot: true })) return true; } return false; }; } function createTitleMatcher(patterns) { const reList = Array.isArray(patterns) ? patterns : [patterns]; return value => { for (const re of reList) { re.lastIndex = 0; if (re.test(value)) return true; } return false; }; } function mergeObjects(a, b, c) { const result = { ...a }; for (const x of [b, c].filter(Boolean)) { for (const [name, value] of Object.entries(x)) { if (!Object.is(value, undefined)) result[name] = value; } } return result; } function forceRegExp(pattern) { const match = pattern.match(/^\/(.*)\/([gi]*)$/); if (match) return new RegExp(match[1], match[2]); return new RegExp(pattern, 'gi'); } function relativeFilePath(file) { if (!_path.default.isAbsolute(file)) return file; return _path.default.relative(process.cwd(), file); } function formatLocation(location) { return relativeFilePath(location.file) + ':' + location.line + ':' + location.column; } function errorWithFile(file, message) { return new Error(`${relativeFilePath(file)}: ${message}`); } function expectTypes(receiver, types, matcherName) { if (typeof receiver !== 'object' || !types.includes(receiver.constructor.name)) { const commaSeparated = types.slice(); const lastType = commaSeparated.pop(); const typesString = commaSeparated.length ? commaSeparated.join(', ') + ' or ' + lastType : lastType; throw new Error(`${matcherName} can be only used with ${typesString} object${types.length > 1 ? 's' : ''}`); } } const windowsFilesystemFriendlyLength = exports.windowsFilesystemFriendlyLength = 60; function trimLongString(s, length = 100) { if (s.length <= length) return s; const hash = (0, _utils.calculateSha1)(s); const middle = `-${hash.substring(0, 5)}-`; const start = Math.floor((length - middle.length) / 2); const end = length - middle.length - start; return s.substring(0, start) + middle + s.slice(-end); } function addSuffixToFilePath(filePath, suffix) { const ext = _path.default.extname(filePath); const base = filePath.substring(0, filePath.length - ext.length); return base + suffix + ext; } function sanitizeFilePathBeforeExtension(filePath) { const ext = _path.default.extname(filePath); const base = filePath.substring(0, filePath.length - ext.length); return (0, _utils.sanitizeForFilePath)(base) + ext; } /** * Returns absolute path contained within parent directory. */ function getContainedPath(parentPath, subPath = '') { const resolvedPath = _path.default.resolve(parentPath, subPath); if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + _path.default.sep)) return resolvedPath; return null; } const debugTest = exports.debugTest = (0, _utilsBundle.debug)('pw:test'); const callLogText = exports.callLogText = _utils.formatCallLog; const folderToPackageJsonPath = new Map(); function getPackageJsonPath(folderPath) { const cached = folderToPackageJsonPath.get(folderPath); if (cached !== undefined) return cached; const packageJsonPath = _path.default.join(folderPath, 'package.json'); if (_fs.default.existsSync(packageJsonPath)) { folderToPackageJsonPath.set(folderPath, packageJsonPath); return packageJsonPath; } const parentFolder = _path.default.dirname(folderPath); if (folderPath === parentFolder) { folderToPackageJsonPath.set(folderPath, ''); return ''; } const result = getPackageJsonPath(parentFolder); folderToPackageJsonPath.set(folderPath, result); return result; } function resolveReporterOutputPath(defaultValue, configDir, configValue) { if (configValue) return _path.default.resolve(configDir, configValue); let basePath = getPackageJsonPath(configDir); basePath = basePath ? _path.default.dirname(basePath) : process.cwd(); return _path.default.resolve(basePath, defaultValue); } async function normalizeAndSaveAttachment(outputPath, name, options = {}) { if (options.path === undefined && options.body === undefined) return { name, contentType: 'text/plain' }; if ((options.path !== undefined ? 1 : 0) + (options.body !== undefined ? 1 : 0) !== 1) throw new Error(`Exactly one of "path" and "body" must be specified`); if (options.path !== undefined) { var _options$contentType; const hash = (0, _utils.calculateSha1)(options.path); if (!(0, _utils.isString)(name)) throw new Error('"name" should be string.'); const sanitizedNamePrefix = (0, _utils.sanitizeForFilePath)(name) + '-'; const dest = _path.default.join(outputPath, 'attachments', sanitizedNamePrefix + hash + _path.default.extname(options.path)); await _fs.default.promises.mkdir(_path.default.dirname(dest), { recursive: true }); await _fs.default.promises.copyFile(options.path, dest); const contentType = (_options$contentType = options.contentType) !== null && _options$contentType !== void 0 ? _options$contentType : _utilsBundle.mime.getType(_path.default.basename(options.path)) || 'application/octet-stream'; return { name, contentType, path: dest }; } else { var _options$contentType2; const contentType = (_options$contentType2 = options.contentType) !== null && _options$contentType2 !== void 0 ? _options$contentType2 : typeof options.body === 'string' ? 'text/plain' : 'application/octet-stream'; return { name, contentType, body: typeof options.body === 'string' ? Buffer.from(options.body) : options.body }; } } function fileIsModule(file) { if (file.endsWith('.mjs') || file.endsWith('.mts')) return true; if (file.endsWith('.cjs') || file.endsWith('.cts')) return false; const folder = _path.default.dirname(file); return folderIsModule(folder); } function folderIsModule(folder) { const packageJsonPath = getPackageJsonPath(folder); if (!packageJsonPath) return false; // Rely on `require` internal caching logic. return require(packageJsonPath).type === 'module'; } const packageJsonMainFieldCache = new Map(); function getMainFieldFromPackageJson(packageJsonPath) { if (!packageJsonMainFieldCache.has(packageJsonPath)) { let mainField; try { mainField = JSON.parse(_fs.default.readFileSync(packageJsonPath, 'utf8')).main; } catch {} packageJsonMainFieldCache.set(packageJsonPath, mainField); } return packageJsonMainFieldCache.get(packageJsonPath); } // This method performs "file extension subsitution" to find the ts, js or similar source file // based on the import specifier, which might or might not have an extension. See TypeScript docs: // https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution. const kExtLookups = new Map([['.js', ['.jsx', '.ts', '.tsx']], ['.jsx', ['.tsx']], ['.cjs', ['.cts']], ['.mjs', ['.mts']], ['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']]]); function resolveImportSpecifierExtension(resolved) { if (fileExists(resolved)) return resolved; for (const [ext, others] of kExtLookups) { if (!resolved.endsWith(ext)) continue; for (const other of others) { const modified = resolved.substring(0, resolved.length - ext.length) + other; if (fileExists(modified)) return modified; } break; // Do not try '' when a more specific extension like '.jsx' matched. } } // This method resolves directory imports and performs "file extension subsitution". // It is intended to be called after the path mapping resolution. // // Directory imports follow the --moduleResolution=bundler strategy from tsc. // https://www.typescriptlang.org/docs/handbook/modules/reference.html#directory-modules-index-file-resolution // https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler // // See also Node.js "folder as module" behavior: // https://nodejs.org/dist/latest-v20.x/docs/api/modules.html#folders-as-modules. function resolveImportSpecifierAfterMapping(resolved, afterPathMapping) { const resolvedFile = resolveImportSpecifierExtension(resolved); if (resolvedFile) return resolvedFile; if (dirExists(resolved)) { const packageJsonPath = _path.default.join(resolved, 'package.json'); if (afterPathMapping) { // Most notably, the module resolution algorithm is not performed after the path mapping. // This means no node_modules lookup or package.json#exports. // // Only the "folder as module" Node.js behavior is respected: // - consult `package.json#main`; // - look for `index.js` or similar. const mainField = getMainFieldFromPackageJson(packageJsonPath); const mainFieldResolved = mainField ? resolveImportSpecifierExtension(_path.default.resolve(resolved, mainField)) : undefined; return mainFieldResolved || resolveImportSpecifierExtension(_path.default.join(resolved, 'index')); } // If we import a package, let Node.js figure out the correct import based on package.json. // This also covers the "main" field for "folder as module". if (fileExists(packageJsonPath)) return resolved; // Implement the "folder as module" Node.js behavior. // Note that we do not delegate to Node.js, because we support this for ESM as well, // following the TypeScript "bundler" mode. const dirImport = _path.default.join(resolved, 'index'); return resolveImportSpecifierExtension(dirImport); } } function fileExists(resolved) { var _fs$statSync; return (_fs$statSync = _fs.default.statSync(resolved, { throwIfNoEntry: false })) === null || _fs$statSync === void 0 ? void 0 : _fs$statSync.isFile(); } function dirExists(resolved) { var _fs$statSync2; return (_fs$statSync2 = _fs.default.statSync(resolved, { throwIfNoEntry: false })) === null || _fs$statSync2 === void 0 ? void 0 : _fs$statSync2.isDirectory(); } async function removeDirAndLogToConsole(dir) { try { if (!_fs.default.existsSync(dir)) return; // eslint-disable-next-line no-console console.log(`Removing ${await _fs.default.promises.realpath(dir)}`); await _fs.default.promises.rm(dir, { recursive: true, force: true }); } catch {} }