- Add Playwright E2E testing framework with cross-browser support (Chrome, Firefox) - Create authentication helpers for CalDAV server integration - Implement calendar interaction helpers with event creation, drag-and-drop, and view switching - Add comprehensive drag-and-drop test suite with event cleanup - Configure CI/CD integration with Gitea Actions for headless testing - Support both local development and CI environments with proper dependency management - Include video recording, screenshots, and HTML reporting for test debugging - Handle Firefox-specific timing and interaction challenges with force clicks and timeouts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			408 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
var __create = Object.create;
 | 
						|
var __defProp = Object.defineProperty;
 | 
						|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 | 
						|
var __getOwnPropNames = Object.getOwnPropertyNames;
 | 
						|
var __getProtoOf = Object.getPrototypeOf;
 | 
						|
var __hasOwnProp = Object.prototype.hasOwnProperty;
 | 
						|
var __export = (target, all) => {
 | 
						|
  for (var name in all)
 | 
						|
    __defProp(target, name, { get: all[name], enumerable: true });
 | 
						|
};
 | 
						|
var __copyProps = (to, from, except, desc) => {
 | 
						|
  if (from && typeof from === "object" || typeof from === "function") {
 | 
						|
    for (let key of __getOwnPropNames(from))
 | 
						|
      if (!__hasOwnProp.call(to, key) && key !== except)
 | 
						|
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
 | 
						|
  }
 | 
						|
  return to;
 | 
						|
};
 | 
						|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
 | 
						|
  // If the importer is in node compatibility mode or this is not an ESM
 | 
						|
  // file that has been converted to a CommonJS file using a Babel-
 | 
						|
  // compatible transform (i.e. "__esModule" has not been set), then set
 | 
						|
  // "default" to the CommonJS "module.exports" for node compatibility.
 | 
						|
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
 | 
						|
  mod
 | 
						|
));
 | 
						|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
 | 
						|
var util_exports = {};
 | 
						|
__export(util_exports, {
 | 
						|
  addSuffixToFilePath: () => addSuffixToFilePath,
 | 
						|
  ansiRegex: () => ansiRegex,
 | 
						|
  callLogText: () => callLogText,
 | 
						|
  createFileFiltersFromArguments: () => createFileFiltersFromArguments,
 | 
						|
  createFileMatcher: () => createFileMatcher,
 | 
						|
  createFileMatcherFromArguments: () => createFileMatcherFromArguments,
 | 
						|
  createTitleMatcher: () => createTitleMatcher,
 | 
						|
  debugTest: () => debugTest,
 | 
						|
  errorWithFile: () => errorWithFile,
 | 
						|
  expectTypes: () => expectTypes,
 | 
						|
  fileExistsAsync: () => fileExistsAsync,
 | 
						|
  fileIsModule: () => fileIsModule,
 | 
						|
  filterStackFile: () => filterStackFile,
 | 
						|
  filterStackTrace: () => filterStackTrace,
 | 
						|
  filteredStackTrace: () => filteredStackTrace,
 | 
						|
  forceRegExp: () => forceRegExp,
 | 
						|
  formatLocation: () => formatLocation,
 | 
						|
  getContainedPath: () => getContainedPath,
 | 
						|
  getPackageJsonPath: () => getPackageJsonPath,
 | 
						|
  mergeObjects: () => mergeObjects,
 | 
						|
  normalizeAndSaveAttachment: () => normalizeAndSaveAttachment,
 | 
						|
  relativeFilePath: () => relativeFilePath,
 | 
						|
  removeDirAndLogToConsole: () => removeDirAndLogToConsole,
 | 
						|
  resolveImportSpecifierAfterMapping: () => resolveImportSpecifierAfterMapping,
 | 
						|
  resolveReporterOutputPath: () => resolveReporterOutputPath,
 | 
						|
  sanitizeFilePathBeforeExtension: () => sanitizeFilePathBeforeExtension,
 | 
						|
  serializeError: () => serializeError,
 | 
						|
  stripAnsiEscapes: () => stripAnsiEscapes,
 | 
						|
  trimLongString: () => trimLongString,
 | 
						|
  windowsFilesystemFriendlyLength: () => windowsFilesystemFriendlyLength
 | 
						|
});
 | 
						|
module.exports = __toCommonJS(util_exports);
 | 
						|
var import_fs = __toESM(require("fs"));
 | 
						|
var import_path = __toESM(require("path"));
 | 
						|
var import_url = __toESM(require("url"));
 | 
						|
var import_util = __toESM(require("util"));
 | 
						|
var import_utils = require("playwright-core/lib/utils");
 | 
						|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
 | 
						|
const PLAYWRIGHT_TEST_PATH = import_path.default.join(__dirname, "..");
 | 
						|
const PLAYWRIGHT_CORE_PATH = import_path.default.dirname(require.resolve("playwright-core/package.json"));
 | 
						|
function filterStackTrace(e) {
 | 
						|
  const name = e.name ? e.name + ": " : "";
 | 
						|
  const cause = e.cause instanceof Error ? filterStackTrace(e.cause) : void 0;
 | 
						|
  if (process.env.PWDEBUGIMPL)
 | 
						|
    return { message: name + e.message, stack: e.stack || "", cause };
 | 
						|
  const stackLines = (0, import_utils.stringifyStackFrames)(filteredStackTrace(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, import_utils.parseStackFrame)(line, import_path.default.sep, !!process.env.PWDEBUGIMPL);
 | 
						|
    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: import_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?.[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, import_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;
 | 
						|
    }
 | 
						|
    if (import_path.default.sep === "\\") {
 | 
						|
      const fileURL = import_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, import_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, void 0))
 | 
						|
        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 (!import_path.default.isAbsolute(file))
 | 
						|
    return file;
 | 
						|
  return import_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 = 60;
 | 
						|
function trimLongString(s, length = 100) {
 | 
						|
  if (s.length <= length)
 | 
						|
    return s;
 | 
						|
  const hash = (0, import_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 = import_path.default.extname(filePath);
 | 
						|
  const base = filePath.substring(0, filePath.length - ext.length);
 | 
						|
  return base + suffix + ext;
 | 
						|
}
 | 
						|
function sanitizeFilePathBeforeExtension(filePath, ext) {
 | 
						|
  ext ??= import_path.default.extname(filePath);
 | 
						|
  const base = filePath.substring(0, filePath.length - ext.length);
 | 
						|
  return (0, import_utils.sanitizeForFilePath)(base) + ext;
 | 
						|
}
 | 
						|
function getContainedPath(parentPath, subPath = "") {
 | 
						|
  const resolvedPath = import_path.default.resolve(parentPath, subPath);
 | 
						|
  if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + import_path.default.sep))
 | 
						|
    return resolvedPath;
 | 
						|
  return null;
 | 
						|
}
 | 
						|
const debugTest = (0, import_utilsBundle.debug)("pw:test");
 | 
						|
const callLogText = (log) => {
 | 
						|
  if (!log || !log.some((l) => !!l))
 | 
						|
    return "";
 | 
						|
  return `
 | 
						|
Call log:
 | 
						|
${import_utilsBundle.colors.dim(log.join("\n"))}
 | 
						|
`;
 | 
						|
};
 | 
						|
const folderToPackageJsonPath = /* @__PURE__ */ new Map();
 | 
						|
function getPackageJsonPath(folderPath) {
 | 
						|
  const cached = folderToPackageJsonPath.get(folderPath);
 | 
						|
  if (cached !== void 0)
 | 
						|
    return cached;
 | 
						|
  const packageJsonPath = import_path.default.join(folderPath, "package.json");
 | 
						|
  if (import_fs.default.existsSync(packageJsonPath)) {
 | 
						|
    folderToPackageJsonPath.set(folderPath, packageJsonPath);
 | 
						|
    return packageJsonPath;
 | 
						|
  }
 | 
						|
  const parentFolder = import_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 import_path.default.resolve(configDir, configValue);
 | 
						|
  let basePath = getPackageJsonPath(configDir);
 | 
						|
  basePath = basePath ? import_path.default.dirname(basePath) : process.cwd();
 | 
						|
  return import_path.default.resolve(basePath, defaultValue);
 | 
						|
}
 | 
						|
async function normalizeAndSaveAttachment(outputPath, name, options = {}) {
 | 
						|
  if (options.path === void 0 && options.body === void 0)
 | 
						|
    return { name, contentType: "text/plain" };
 | 
						|
  if ((options.path !== void 0 ? 1 : 0) + (options.body !== void 0 ? 1 : 0) !== 1)
 | 
						|
    throw new Error(`Exactly one of "path" and "body" must be specified`);
 | 
						|
  if (options.path !== void 0) {
 | 
						|
    const hash = (0, import_utils.calculateSha1)(options.path);
 | 
						|
    if (!(0, import_utils.isString)(name))
 | 
						|
      throw new Error('"name" should be string.');
 | 
						|
    const sanitizedNamePrefix = (0, import_utils.sanitizeForFilePath)(name) + "-";
 | 
						|
    const dest = import_path.default.join(outputPath, "attachments", sanitizedNamePrefix + hash + import_path.default.extname(options.path));
 | 
						|
    await import_fs.default.promises.mkdir(import_path.default.dirname(dest), { recursive: true });
 | 
						|
    await import_fs.default.promises.copyFile(options.path, dest);
 | 
						|
    const contentType = options.contentType ?? (import_utilsBundle.mime.getType(import_path.default.basename(options.path)) || "application/octet-stream");
 | 
						|
    return { name, contentType, path: dest };
 | 
						|
  } else {
 | 
						|
    const contentType = options.contentType ?? (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 = import_path.default.dirname(file);
 | 
						|
  return folderIsModule(folder);
 | 
						|
}
 | 
						|
function folderIsModule(folder) {
 | 
						|
  const packageJsonPath = getPackageJsonPath(folder);
 | 
						|
  if (!packageJsonPath)
 | 
						|
    return false;
 | 
						|
  return require(packageJsonPath).type === "module";
 | 
						|
}
 | 
						|
const packageJsonMainFieldCache = /* @__PURE__ */ new Map();
 | 
						|
function getMainFieldFromPackageJson(packageJsonPath) {
 | 
						|
  if (!packageJsonMainFieldCache.has(packageJsonPath)) {
 | 
						|
    let mainField;
 | 
						|
    try {
 | 
						|
      mainField = JSON.parse(import_fs.default.readFileSync(packageJsonPath, "utf8")).main;
 | 
						|
    } catch {
 | 
						|
    }
 | 
						|
    packageJsonMainFieldCache.set(packageJsonPath, mainField);
 | 
						|
  }
 | 
						|
  return packageJsonMainFieldCache.get(packageJsonPath);
 | 
						|
}
 | 
						|
const kExtLookups = /* @__PURE__ */ 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;
 | 
						|
  }
 | 
						|
}
 | 
						|
function resolveImportSpecifierAfterMapping(resolved, afterPathMapping) {
 | 
						|
  const resolvedFile = resolveImportSpecifierExtension(resolved);
 | 
						|
  if (resolvedFile)
 | 
						|
    return resolvedFile;
 | 
						|
  if (dirExists(resolved)) {
 | 
						|
    const packageJsonPath = import_path.default.join(resolved, "package.json");
 | 
						|
    if (afterPathMapping) {
 | 
						|
      const mainField = getMainFieldFromPackageJson(packageJsonPath);
 | 
						|
      const mainFieldResolved = mainField ? resolveImportSpecifierExtension(import_path.default.resolve(resolved, mainField)) : void 0;
 | 
						|
      return mainFieldResolved || resolveImportSpecifierExtension(import_path.default.join(resolved, "index"));
 | 
						|
    }
 | 
						|
    if (fileExists(packageJsonPath))
 | 
						|
      return resolved;
 | 
						|
    const dirImport = import_path.default.join(resolved, "index");
 | 
						|
    return resolveImportSpecifierExtension(dirImport);
 | 
						|
  }
 | 
						|
}
 | 
						|
function fileExists(resolved) {
 | 
						|
  return import_fs.default.statSync(resolved, { throwIfNoEntry: false })?.isFile();
 | 
						|
}
 | 
						|
async function fileExistsAsync(resolved) {
 | 
						|
  try {
 | 
						|
    const stat = await import_fs.default.promises.stat(resolved);
 | 
						|
    return stat.isFile();
 | 
						|
  } catch {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
}
 | 
						|
function dirExists(resolved) {
 | 
						|
  return import_fs.default.statSync(resolved, { throwIfNoEntry: false })?.isDirectory();
 | 
						|
}
 | 
						|
async function removeDirAndLogToConsole(dir) {
 | 
						|
  try {
 | 
						|
    if (!import_fs.default.existsSync(dir))
 | 
						|
      return;
 | 
						|
    console.log(`Removing ${await import_fs.default.promises.realpath(dir)}`);
 | 
						|
    await import_fs.default.promises.rm(dir, { recursive: true, force: true });
 | 
						|
  } catch {
 | 
						|
  }
 | 
						|
}
 | 
						|
const ansiRegex = new RegExp("([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))", "g");
 | 
						|
function stripAnsiEscapes(str) {
 | 
						|
  return str.replace(ansiRegex, "");
 | 
						|
}
 | 
						|
// Annotate the CommonJS export names for ESM import in node:
 | 
						|
0 && (module.exports = {
 | 
						|
  addSuffixToFilePath,
 | 
						|
  ansiRegex,
 | 
						|
  callLogText,
 | 
						|
  createFileFiltersFromArguments,
 | 
						|
  createFileMatcher,
 | 
						|
  createFileMatcherFromArguments,
 | 
						|
  createTitleMatcher,
 | 
						|
  debugTest,
 | 
						|
  errorWithFile,
 | 
						|
  expectTypes,
 | 
						|
  fileExistsAsync,
 | 
						|
  fileIsModule,
 | 
						|
  filterStackFile,
 | 
						|
  filterStackTrace,
 | 
						|
  filteredStackTrace,
 | 
						|
  forceRegExp,
 | 
						|
  formatLocation,
 | 
						|
  getContainedPath,
 | 
						|
  getPackageJsonPath,
 | 
						|
  mergeObjects,
 | 
						|
  normalizeAndSaveAttachment,
 | 
						|
  relativeFilePath,
 | 
						|
  removeDirAndLogToConsole,
 | 
						|
  resolveImportSpecifierAfterMapping,
 | 
						|
  resolveReporterOutputPath,
 | 
						|
  sanitizeFilePathBeforeExtension,
 | 
						|
  serializeError,
 | 
						|
  stripAnsiEscapes,
 | 
						|
  trimLongString,
 | 
						|
  windowsFilesystemFriendlyLength
 | 
						|
});
 |