Implement comprehensive frontend integration testing with Playwright
- 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>
This commit is contained in:
		
							
								
								
									
										297
									
								
								frontend/e2e/node_modules/playwright/lib/runner/loadUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								frontend/e2e/node_modules/playwright/lib/runner/loadUtils.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,297 @@ | ||||
| "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 loadUtils_exports = {}; | ||||
| __export(loadUtils_exports, { | ||||
|   collectProjectsAndTestFiles: () => collectProjectsAndTestFiles, | ||||
|   createRootSuite: () => createRootSuite, | ||||
|   loadFileSuites: () => loadFileSuites, | ||||
|   loadGlobalHook: () => loadGlobalHook, | ||||
|   loadReporter: () => loadReporter | ||||
| }); | ||||
| module.exports = __toCommonJS(loadUtils_exports); | ||||
| var import_path = __toESM(require("path")); | ||||
| var import_loaderHost = require("./loaderHost"); | ||||
| var import_util = require("../util"); | ||||
| var import_projectUtils = require("./projectUtils"); | ||||
| var import_testGroups = require("./testGroups"); | ||||
| var import_suiteUtils = require("../common/suiteUtils"); | ||||
| var import_test = require("../common/test"); | ||||
| var import_compilationCache = require("../transform/compilationCache"); | ||||
| var import_transform = require("../transform/transform"); | ||||
| var import_utilsBundle = require("../utilsBundle"); | ||||
| async function collectProjectsAndTestFiles(testRun, doNotRunTestsOutsideProjectFilter) { | ||||
|   const config = testRun.config; | ||||
|   const fsCache = /* @__PURE__ */ new Map(); | ||||
|   const sourceMapCache = /* @__PURE__ */ new Map(); | ||||
|   const cliFileMatcher = config.cliArgs.length ? (0, import_util.createFileMatcherFromArguments)(config.cliArgs) : null; | ||||
|   const allFilesForProject = /* @__PURE__ */ new Map(); | ||||
|   const filteredProjects = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter); | ||||
|   for (const project of filteredProjects) { | ||||
|     const files = await (0, import_projectUtils.collectFilesForProject)(project, fsCache); | ||||
|     allFilesForProject.set(project, files); | ||||
|   } | ||||
|   const filesToRunByProject = /* @__PURE__ */ new Map(); | ||||
|   for (const [project, files] of allFilesForProject) { | ||||
|     const matchedFiles = files.filter((file) => { | ||||
|       const hasMatchingSources = sourceMapSources(file, sourceMapCache).some((source) => { | ||||
|         if (cliFileMatcher && !cliFileMatcher(source)) | ||||
|           return false; | ||||
|         return true; | ||||
|       }); | ||||
|       return hasMatchingSources; | ||||
|     }); | ||||
|     const filteredFiles = matchedFiles.filter(Boolean); | ||||
|     filesToRunByProject.set(project, filteredFiles); | ||||
|   } | ||||
|   const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]); | ||||
|   for (const [project, type] of projectClosure) { | ||||
|     if (type === "dependency") { | ||||
|       const treatProjectAsEmpty = doNotRunTestsOutsideProjectFilter && !filteredProjects.includes(project); | ||||
|       const files = treatProjectAsEmpty ? [] : allFilesForProject.get(project) || await (0, import_projectUtils.collectFilesForProject)(project, fsCache); | ||||
|       filesToRunByProject.set(project, files); | ||||
|     } | ||||
|   } | ||||
|   testRun.projectFiles = filesToRunByProject; | ||||
|   testRun.projectSuites = /* @__PURE__ */ new Map(); | ||||
| } | ||||
| async function loadFileSuites(testRun, mode, errors) { | ||||
|   const config = testRun.config; | ||||
|   const allTestFiles = /* @__PURE__ */ new Set(); | ||||
|   for (const files of testRun.projectFiles.values()) | ||||
|     files.forEach((file) => allTestFiles.add(file)); | ||||
|   const fileSuiteByFile = /* @__PURE__ */ new Map(); | ||||
|   const loaderHost = mode === "out-of-process" ? new import_loaderHost.OutOfProcessLoaderHost(config) : new import_loaderHost.InProcessLoaderHost(config); | ||||
|   if (await loaderHost.start(errors)) { | ||||
|     for (const file of allTestFiles) { | ||||
|       const fileSuite = await loaderHost.loadTestFile(file, errors); | ||||
|       fileSuiteByFile.set(file, fileSuite); | ||||
|       errors.push(...createDuplicateTitlesErrors(config, fileSuite)); | ||||
|     } | ||||
|     await loaderHost.stop(); | ||||
|   } | ||||
|   for (const file of allTestFiles) { | ||||
|     for (const dependency of (0, import_compilationCache.dependenciesForTestFile)(file)) { | ||||
|       if (allTestFiles.has(dependency)) { | ||||
|         const importer = import_path.default.relative(config.config.rootDir, file); | ||||
|         const importee = import_path.default.relative(config.config.rootDir, dependency); | ||||
|         errors.push({ | ||||
|           message: `Error: test file "${importer}" should not import test file "${importee}"`, | ||||
|           location: { file, line: 1, column: 1 } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   for (const [project, files] of testRun.projectFiles) { | ||||
|     const suites = files.map((file) => fileSuiteByFile.get(file)).filter(Boolean); | ||||
|     testRun.projectSuites.set(project, suites); | ||||
|   } | ||||
| } | ||||
| async function createRootSuite(testRun, errors, shouldFilterOnly) { | ||||
|   const config = testRun.config; | ||||
|   const rootSuite = new import_test.Suite("", "root"); | ||||
|   const projectSuites = /* @__PURE__ */ new Map(); | ||||
|   const filteredProjectSuites = /* @__PURE__ */ new Map(); | ||||
|   { | ||||
|     const cliFileFilters = (0, import_util.createFileFiltersFromArguments)(config.cliArgs); | ||||
|     const grepMatcher = config.cliGrep ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrep)) : () => true; | ||||
|     const grepInvertMatcher = config.cliGrepInvert ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrepInvert)) : () => false; | ||||
|     const cliTitleMatcher = (title) => !grepInvertMatcher(title) && grepMatcher(title); | ||||
|     for (const [project, fileSuites] of testRun.projectSuites) { | ||||
|       const projectSuite = createProjectSuite(project, fileSuites); | ||||
|       projectSuites.set(project, projectSuite); | ||||
|       const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testFilters: config.preOnlyTestFilters }); | ||||
|       filteredProjectSuites.set(project, filteredProjectSuite); | ||||
|     } | ||||
|   } | ||||
|   if (shouldFilterOnly) { | ||||
|     const filteredRoot = new import_test.Suite("", "root"); | ||||
|     for (const filteredProjectSuite of filteredProjectSuites.values()) | ||||
|       filteredRoot._addSuite(filteredProjectSuite); | ||||
|     (0, import_suiteUtils.filterOnly)(filteredRoot); | ||||
|     for (const [project, filteredProjectSuite] of filteredProjectSuites) { | ||||
|       if (!filteredRoot.suites.includes(filteredProjectSuite)) | ||||
|         filteredProjectSuites.delete(project); | ||||
|     } | ||||
|   } | ||||
|   const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filteredProjectSuites.keys()], (project) => filteredProjectSuites.get(project)._hasTests()); | ||||
|   for (const [project, type] of projectClosure) { | ||||
|     if (type === "top-level") { | ||||
|       project.project.repeatEach = project.fullConfig.configCLIOverrides.repeatEach ?? project.project.repeatEach; | ||||
|       rootSuite._addSuite(buildProjectSuite(project, filteredProjectSuites.get(project))); | ||||
|     } | ||||
|   } | ||||
|   if (config.config.forbidOnly) { | ||||
|     const onlyTestsAndSuites = rootSuite._getOnlyItems(); | ||||
|     if (onlyTestsAndSuites.length > 0) { | ||||
|       const configFilePath = config.config.configFile ? import_path.default.relative(config.config.rootDir, config.config.configFile) : void 0; | ||||
|       errors.push(...createForbidOnlyErrors(onlyTestsAndSuites, config.configCLIOverrides.forbidOnly, configFilePath)); | ||||
|     } | ||||
|   } | ||||
|   if (config.config.shard) { | ||||
|     const testGroups = []; | ||||
|     for (const projectSuite of rootSuite.suites) { | ||||
|       testGroups.push(...(0, import_testGroups.createTestGroups)(projectSuite, config.config.shard.total)); | ||||
|     } | ||||
|     const testGroupsInThisShard = (0, import_testGroups.filterForShard)(config.config.shard, testGroups); | ||||
|     const testsInThisShard = /* @__PURE__ */ new Set(); | ||||
|     for (const group of testGroupsInThisShard) { | ||||
|       for (const test of group.tests) | ||||
|         testsInThisShard.add(test); | ||||
|     } | ||||
|     (0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => testsInThisShard.has(test)); | ||||
|   } | ||||
|   if (config.postShardTestFilters.length) | ||||
|     (0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test))); | ||||
|   { | ||||
|     const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject))); | ||||
|     for (const [project, level] of projectClosure2.entries()) { | ||||
|       if (level === "dependency") | ||||
|         rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project))); | ||||
|     } | ||||
|   } | ||||
|   return rootSuite; | ||||
| } | ||||
| function createProjectSuite(project, fileSuites) { | ||||
|   const projectSuite = new import_test.Suite(project.project.name, "project"); | ||||
|   for (const fileSuite of fileSuites) | ||||
|     projectSuite._addSuite((0, import_suiteUtils.bindFileSuiteToProject)(project, fileSuite)); | ||||
|   const grepMatcher = (0, import_util.createTitleMatcher)(project.project.grep); | ||||
|   const grepInvertMatcher = project.project.grepInvert ? (0, import_util.createTitleMatcher)(project.project.grepInvert) : null; | ||||
|   (0, import_suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, (test) => { | ||||
|     const grepTitle = test._grepTitleWithTags(); | ||||
|     if (grepInvertMatcher?.(grepTitle)) | ||||
|       return false; | ||||
|     return grepMatcher(grepTitle); | ||||
|   }); | ||||
|   return projectSuite; | ||||
| } | ||||
| function filterProjectSuite(projectSuite, options) { | ||||
|   if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testFilters.length) | ||||
|     return projectSuite; | ||||
|   const result = projectSuite._deepClone(); | ||||
|   if (options.cliFileFilters.length) | ||||
|     (0, import_suiteUtils.filterByFocusedLine)(result, options.cliFileFilters); | ||||
|   (0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => { | ||||
|     if (!options.testFilters.every((filter) => filter(test))) | ||||
|       return false; | ||||
|     if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags())) | ||||
|       return false; | ||||
|     return true; | ||||
|   }); | ||||
|   return result; | ||||
| } | ||||
| function buildProjectSuite(project, projectSuite) { | ||||
|   const result = new import_test.Suite(project.project.name, "project"); | ||||
|   result._fullProject = project; | ||||
|   if (project.fullyParallel) | ||||
|     result._parallelMode = "parallel"; | ||||
|   for (const fileSuite of projectSuite.suites) { | ||||
|     result._addSuite(fileSuite); | ||||
|     for (let repeatEachIndex = 1; repeatEachIndex < project.project.repeatEach; repeatEachIndex++) { | ||||
|       const clone = fileSuite._deepClone(); | ||||
|       (0, import_suiteUtils.applyRepeatEachIndex)(project, clone, repeatEachIndex); | ||||
|       result._addSuite(clone); | ||||
|     } | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| function createForbidOnlyErrors(onlyTestsAndSuites, forbidOnlyCLIFlag, configFilePath) { | ||||
|   const errors = []; | ||||
|   for (const testOrSuite of onlyTestsAndSuites) { | ||||
|     const title = testOrSuite.titlePath().slice(2).join(" "); | ||||
|     const configFilePathName = configFilePath ? `'${configFilePath}'` : "the Playwright configuration file"; | ||||
|     const forbidOnlySource = forbidOnlyCLIFlag ? `'--forbid-only' CLI flag` : `'forbidOnly' option in ${configFilePathName}`; | ||||
|     const error = { | ||||
|       message: `Error: item focused with '.only' is not allowed due to the ${forbidOnlySource}: "${title}"`, | ||||
|       location: testOrSuite.location | ||||
|     }; | ||||
|     errors.push(error); | ||||
|   } | ||||
|   return errors; | ||||
| } | ||||
| function createDuplicateTitlesErrors(config, fileSuite) { | ||||
|   const errors = []; | ||||
|   const testsByFullTitle = /* @__PURE__ */ new Map(); | ||||
|   for (const test of fileSuite.allTests()) { | ||||
|     const fullTitle = test.titlePath().slice(1).join(" \u203A "); | ||||
|     const existingTest = testsByFullTitle.get(fullTitle); | ||||
|     if (existingTest) { | ||||
|       const error = { | ||||
|         message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.config.rootDir, existingTest)}`, | ||||
|         location: test.location | ||||
|       }; | ||||
|       errors.push(error); | ||||
|     } | ||||
|     testsByFullTitle.set(fullTitle, test); | ||||
|   } | ||||
|   return errors; | ||||
| } | ||||
| function buildItemLocation(rootDir, testOrSuite) { | ||||
|   if (!testOrSuite.location) | ||||
|     return ""; | ||||
|   return `${import_path.default.relative(rootDir, testOrSuite.location.file)}:${testOrSuite.location.line}`; | ||||
| } | ||||
| async function requireOrImportDefaultFunction(file, expectConstructor) { | ||||
|   let func = await (0, import_transform.requireOrImport)(file); | ||||
|   if (func && typeof func === "object" && "default" in func) | ||||
|     func = func["default"]; | ||||
|   if (typeof func !== "function") | ||||
|     throw (0, import_util.errorWithFile)(file, `file must export a single ${expectConstructor ? "class" : "function"}.`); | ||||
|   return func; | ||||
| } | ||||
| function loadGlobalHook(config, file) { | ||||
|   return requireOrImportDefaultFunction(import_path.default.resolve(config.config.rootDir, file), false); | ||||
| } | ||||
| function loadReporter(config, file) { | ||||
|   return requireOrImportDefaultFunction(config ? import_path.default.resolve(config.config.rootDir, file) : file, true); | ||||
| } | ||||
| function sourceMapSources(file, cache) { | ||||
|   let sources = [file]; | ||||
|   if (!file.endsWith(".js")) | ||||
|     return sources; | ||||
|   if (cache.has(file)) | ||||
|     return cache.get(file); | ||||
|   try { | ||||
|     const sourceMap = import_utilsBundle.sourceMapSupport.retrieveSourceMap(file); | ||||
|     const sourceMapData = typeof sourceMap?.map === "string" ? JSON.parse(sourceMap.map) : sourceMap?.map; | ||||
|     if (sourceMapData?.sources) | ||||
|       sources = sourceMapData.sources.map((source) => import_path.default.resolve(import_path.default.dirname(file), source)); | ||||
|   } finally { | ||||
|     cache.set(file, sources); | ||||
|     return sources; | ||||
|   } | ||||
| } | ||||
| // Annotate the CommonJS export names for ESM import in node: | ||||
| 0 && (module.exports = { | ||||
|   collectProjectsAndTestFiles, | ||||
|   createRootSuite, | ||||
|   loadFileSuites, | ||||
|   loadGlobalHook, | ||||
|   loadReporter | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone