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:
		
							
								
								
									
										399
									
								
								frontend/e2e/node_modules/playwright/lib/runner/tasks.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								frontend/e2e/node_modules/playwright/lib/runner/tasks.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,399 @@ | ||||
| "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 tasks_exports = {}; | ||||
| __export(tasks_exports, { | ||||
|   TestRun: () => TestRun, | ||||
|   createApplyRebaselinesTask: () => createApplyRebaselinesTask, | ||||
|   createClearCacheTask: () => createClearCacheTask, | ||||
|   createGlobalSetupTasks: () => createGlobalSetupTasks, | ||||
|   createListFilesTask: () => createListFilesTask, | ||||
|   createLoadTask: () => createLoadTask, | ||||
|   createPluginSetupTasks: () => createPluginSetupTasks, | ||||
|   createReportBeginTask: () => createReportBeginTask, | ||||
|   createRunTestsTasks: () => createRunTestsTasks, | ||||
|   createStartDevServerTask: () => createStartDevServerTask, | ||||
|   runTasks: () => runTasks, | ||||
|   runTasksDeferCleanup: () => runTasksDeferCleanup | ||||
| }); | ||||
| module.exports = __toCommonJS(tasks_exports); | ||||
| var import_fs = __toESM(require("fs")); | ||||
| var import_path = __toESM(require("path")); | ||||
| var import_util = require("util"); | ||||
| var import_utils = require("playwright-core/lib/utils"); | ||||
| var import_utilsBundle = require("playwright-core/lib/utilsBundle"); | ||||
| var import_dispatcher = require("./dispatcher"); | ||||
| var import_failureTracker = require("./failureTracker"); | ||||
| var import_loadUtils = require("./loadUtils"); | ||||
| var import_projectUtils = require("./projectUtils"); | ||||
| var import_rebase = require("./rebase"); | ||||
| var import_taskRunner = require("./taskRunner"); | ||||
| var import_vcs = require("./vcs"); | ||||
| var import_test = require("../common/test"); | ||||
| var import_testGroups = require("../runner/testGroups"); | ||||
| var import_compilationCache = require("../transform/compilationCache"); | ||||
| var import_util2 = require("../util"); | ||||
| const readDirAsync = (0, import_util.promisify)(import_fs.default.readdir); | ||||
| class TestRun { | ||||
|   constructor(config, reporter) { | ||||
|     this.rootSuite = void 0; | ||||
|     this.phases = []; | ||||
|     this.projectFiles = /* @__PURE__ */ new Map(); | ||||
|     this.projectSuites = /* @__PURE__ */ new Map(); | ||||
|     this.config = config; | ||||
|     this.reporter = reporter; | ||||
|     this.failureTracker = new import_failureTracker.FailureTracker(config); | ||||
|   } | ||||
| } | ||||
| async function runTasks(testRun, tasks, globalTimeout, cancelPromise) { | ||||
|   const deadline = globalTimeout ? (0, import_utils.monotonicTime)() + globalTimeout : 0; | ||||
|   const taskRunner = new import_taskRunner.TaskRunner(testRun.reporter, globalTimeout || 0); | ||||
|   for (const task of tasks) | ||||
|     taskRunner.addTask(task); | ||||
|   testRun.reporter.onConfigure(testRun.config.config); | ||||
|   const status = await taskRunner.run(testRun, deadline, cancelPromise); | ||||
|   return await finishTaskRun(testRun, status); | ||||
| } | ||||
| async function runTasksDeferCleanup(testRun, tasks) { | ||||
|   const taskRunner = new import_taskRunner.TaskRunner(testRun.reporter, 0); | ||||
|   for (const task of tasks) | ||||
|     taskRunner.addTask(task); | ||||
|   testRun.reporter.onConfigure(testRun.config.config); | ||||
|   const { status, cleanup } = await taskRunner.runDeferCleanup(testRun, 0); | ||||
|   return { status: await finishTaskRun(testRun, status), cleanup }; | ||||
| } | ||||
| async function finishTaskRun(testRun, status) { | ||||
|   if (status === "passed") | ||||
|     status = testRun.failureTracker.result(); | ||||
|   const modifiedResult = await testRun.reporter.onEnd({ status }); | ||||
|   if (modifiedResult && modifiedResult.status) | ||||
|     status = modifiedResult.status; | ||||
|   await testRun.reporter.onExit(); | ||||
|   return status; | ||||
| } | ||||
| function createGlobalSetupTasks(config) { | ||||
|   const tasks = []; | ||||
|   if (!config.configCLIOverrides.preserveOutputDir) | ||||
|     tasks.push(createRemoveOutputDirsTask()); | ||||
|   tasks.push( | ||||
|     ...createPluginSetupTasks(config), | ||||
|     ...config.globalTeardowns.map((file) => createGlobalTeardownTask(file, config)).reverse(), | ||||
|     ...config.globalSetups.map((file) => createGlobalSetupTask(file, config)) | ||||
|   ); | ||||
|   return tasks; | ||||
| } | ||||
| function createRunTestsTasks(config) { | ||||
|   return [ | ||||
|     createPhasesTask(), | ||||
|     createReportBeginTask(), | ||||
|     ...config.plugins.map((plugin) => createPluginBeginTask(plugin)), | ||||
|     createRunTestsTask() | ||||
|   ]; | ||||
| } | ||||
| function createClearCacheTask(config) { | ||||
|   return { | ||||
|     title: "clear cache", | ||||
|     setup: async () => { | ||||
|       await (0, import_util2.removeDirAndLogToConsole)(import_compilationCache.cacheDir); | ||||
|       for (const plugin of config.plugins) | ||||
|         await plugin.instance?.clearCache?.(); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createReportBeginTask() { | ||||
|   return { | ||||
|     title: "report begin", | ||||
|     setup: async (testRun) => { | ||||
|       testRun.reporter.onBegin?.(testRun.rootSuite); | ||||
|     }, | ||||
|     teardown: async ({}) => { | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createPluginSetupTasks(config) { | ||||
|   return config.plugins.map((plugin) => ({ | ||||
|     title: "plugin setup", | ||||
|     setup: async ({ reporter }) => { | ||||
|       if (typeof plugin.factory === "function") | ||||
|         plugin.instance = await plugin.factory(); | ||||
|       else | ||||
|         plugin.instance = plugin.factory; | ||||
|       await plugin.instance?.setup?.(config.config, config.configDir, reporter); | ||||
|     }, | ||||
|     teardown: async () => { | ||||
|       await plugin.instance?.teardown?.(); | ||||
|     } | ||||
|   })); | ||||
| } | ||||
| function createPluginBeginTask(plugin) { | ||||
|   return { | ||||
|     title: "plugin begin", | ||||
|     setup: async (testRun) => { | ||||
|       await plugin.instance?.begin?.(testRun.rootSuite); | ||||
|     }, | ||||
|     teardown: async () => { | ||||
|       await plugin.instance?.end?.(); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createGlobalSetupTask(file, config) { | ||||
|   let title = "global setup"; | ||||
|   if (config.globalSetups.length > 1) | ||||
|     title += ` (${file})`; | ||||
|   let globalSetupResult; | ||||
|   return { | ||||
|     title, | ||||
|     setup: async ({ config: config2 }) => { | ||||
|       const setupHook = await (0, import_loadUtils.loadGlobalHook)(config2, file); | ||||
|       globalSetupResult = await setupHook(config2.config); | ||||
|     }, | ||||
|     teardown: async () => { | ||||
|       if (typeof globalSetupResult === "function") | ||||
|         await globalSetupResult(); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createGlobalTeardownTask(file, config) { | ||||
|   let title = "global teardown"; | ||||
|   if (config.globalTeardowns.length > 1) | ||||
|     title += ` (${file})`; | ||||
|   return { | ||||
|     title, | ||||
|     teardown: async ({ config: config2 }) => { | ||||
|       const teardownHook = await (0, import_loadUtils.loadGlobalHook)(config2, file); | ||||
|       await teardownHook(config2.config); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createRemoveOutputDirsTask() { | ||||
|   return { | ||||
|     title: "clear output", | ||||
|     setup: async ({ config }) => { | ||||
|       const outputDirs = /* @__PURE__ */ new Set(); | ||||
|       const projects = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter); | ||||
|       projects.forEach((p) => outputDirs.add(p.project.outputDir)); | ||||
|       await Promise.all(Array.from(outputDirs).map((outputDir) => (0, import_utils.removeFolders)([outputDir]).then(async ([error]) => { | ||||
|         if (!error) | ||||
|           return; | ||||
|         if (error.code === "EBUSY") { | ||||
|           const entries = await readDirAsync(outputDir).catch((e) => []); | ||||
|           await Promise.all(entries.map((entry) => (0, import_utils.removeFolders)([import_path.default.join(outputDir, entry)]))); | ||||
|         } else { | ||||
|           throw error; | ||||
|         } | ||||
|       }))); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createListFilesTask() { | ||||
|   return { | ||||
|     title: "load tests", | ||||
|     setup: async (testRun, errors) => { | ||||
|       testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, errors, false); | ||||
|       testRun.failureTracker.onRootSuite(testRun.rootSuite); | ||||
|       await (0, import_loadUtils.collectProjectsAndTestFiles)(testRun, false); | ||||
|       for (const [project, files] of testRun.projectFiles) { | ||||
|         const projectSuite = new import_test.Suite(project.project.name, "project"); | ||||
|         projectSuite._fullProject = project; | ||||
|         testRun.rootSuite._addSuite(projectSuite); | ||||
|         const suites = files.map((file) => { | ||||
|           const title = import_path.default.relative(testRun.config.config.rootDir, file); | ||||
|           const suite = new import_test.Suite(title, "file"); | ||||
|           suite.location = { file, line: 0, column: 0 }; | ||||
|           projectSuite._addSuite(suite); | ||||
|           return suite; | ||||
|         }); | ||||
|         testRun.projectSuites.set(project, suites); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createLoadTask(mode, options) { | ||||
|   return { | ||||
|     title: "load tests", | ||||
|     setup: async (testRun, errors, softErrors) => { | ||||
|       await (0, import_loadUtils.collectProjectsAndTestFiles)(testRun, !!options.doNotRunDepsOutsideProjectFilter); | ||||
|       await (0, import_loadUtils.loadFileSuites)(testRun, mode, options.failOnLoadErrors ? errors : softErrors); | ||||
|       if (testRun.config.cliOnlyChanged || options.populateDependencies) { | ||||
|         for (const plugin of testRun.config.plugins) | ||||
|           await plugin.instance?.populateDependencies?.(); | ||||
|       } | ||||
|       if (testRun.config.cliOnlyChanged) { | ||||
|         const changedFiles = await (0, import_vcs.detectChangedTestFiles)(testRun.config.cliOnlyChanged, testRun.config.configDir); | ||||
|         testRun.config.preOnlyTestFilters.push((test) => changedFiles.has(test.location.file)); | ||||
|       } | ||||
|       testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly); | ||||
|       testRun.failureTracker.onRootSuite(testRun.rootSuite); | ||||
|       if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard && !testRun.config.cliOnlyChanged) { | ||||
|         if (testRun.config.cliArgs.length) { | ||||
|           throw new Error([ | ||||
|             `No tests found.`, | ||||
|             `Make sure that arguments are regular expressions matching test files.`, | ||||
|             `You may need to escape symbols like "$" or "*" and quote the arguments.` | ||||
|           ].join("\n")); | ||||
|         } | ||||
|         throw new Error(`No tests found`); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createApplyRebaselinesTask() { | ||||
|   return { | ||||
|     title: "apply rebaselines", | ||||
|     setup: async () => { | ||||
|       (0, import_rebase.clearSuggestedRebaselines)(); | ||||
|     }, | ||||
|     teardown: async ({ config, reporter }) => { | ||||
|       await (0, import_rebase.applySuggestedRebaselines)(config, reporter); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createPhasesTask() { | ||||
|   return { | ||||
|     title: "create phases", | ||||
|     setup: async (testRun) => { | ||||
|       let maxConcurrentTestGroups = 0; | ||||
|       const processed = /* @__PURE__ */ new Set(); | ||||
|       const projectToSuite = new Map(testRun.rootSuite.suites.map((suite) => [suite._fullProject, suite])); | ||||
|       const allProjects = [...projectToSuite.keys()]; | ||||
|       const teardownToSetups = (0, import_projectUtils.buildTeardownToSetupsMap)(allProjects); | ||||
|       const teardownToSetupsDependents = /* @__PURE__ */ new Map(); | ||||
|       for (const [teardown, setups] of teardownToSetups) { | ||||
|         const closure = (0, import_projectUtils.buildDependentProjects)(setups, allProjects); | ||||
|         closure.delete(teardown); | ||||
|         teardownToSetupsDependents.set(teardown, [...closure]); | ||||
|       } | ||||
|       for (let i = 0; i < projectToSuite.size; i++) { | ||||
|         const phaseProjects = []; | ||||
|         for (const project of projectToSuite.keys()) { | ||||
|           if (processed.has(project)) | ||||
|             continue; | ||||
|           const projectsThatShouldFinishFirst = [...project.deps, ...teardownToSetupsDependents.get(project) || []]; | ||||
|           if (projectsThatShouldFinishFirst.find((p) => !processed.has(p))) | ||||
|             continue; | ||||
|           phaseProjects.push(project); | ||||
|         } | ||||
|         for (const project of phaseProjects) | ||||
|           processed.add(project); | ||||
|         if (phaseProjects.length) { | ||||
|           let testGroupsInPhase = 0; | ||||
|           const phase = { dispatcher: new import_dispatcher.Dispatcher(testRun.config, testRun.reporter, testRun.failureTracker), projects: [] }; | ||||
|           testRun.phases.push(phase); | ||||
|           for (const project of phaseProjects) { | ||||
|             const projectSuite = projectToSuite.get(project); | ||||
|             const testGroups = (0, import_testGroups.createTestGroups)(projectSuite, testRun.config.config.workers); | ||||
|             phase.projects.push({ project, projectSuite, testGroups }); | ||||
|             testGroupsInPhase += Math.min(project.workers ?? Number.MAX_SAFE_INTEGER, testGroups.length); | ||||
|           } | ||||
|           (0, import_utilsBundle.debug)("pw:test:task")(`created phase #${testRun.phases.length} with ${phase.projects.map((p) => p.project.project.name).sort()} projects, ${testGroupsInPhase} testGroups`); | ||||
|           maxConcurrentTestGroups = Math.max(maxConcurrentTestGroups, testGroupsInPhase); | ||||
|         } | ||||
|       } | ||||
|       testRun.config.config.metadata.actualWorkers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createRunTestsTask() { | ||||
|   return { | ||||
|     title: "test suite", | ||||
|     setup: async ({ phases, failureTracker }) => { | ||||
|       const successfulProjects = /* @__PURE__ */ new Set(); | ||||
|       const extraEnvByProjectId = /* @__PURE__ */ new Map(); | ||||
|       const teardownToSetups = (0, import_projectUtils.buildTeardownToSetupsMap)(phases.map((phase) => phase.projects.map((p) => p.project)).flat()); | ||||
|       for (const { dispatcher, projects } of phases) { | ||||
|         const phaseTestGroups = []; | ||||
|         for (const { project, testGroups } of projects) { | ||||
|           let extraEnv = {}; | ||||
|           for (const dep of project.deps) | ||||
|             extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep.id) }; | ||||
|           for (const setup of teardownToSetups.get(project) || []) | ||||
|             extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(setup.id) }; | ||||
|           extraEnvByProjectId.set(project.id, extraEnv); | ||||
|           const hasFailedDeps = project.deps.some((p) => !successfulProjects.has(p)); | ||||
|           if (!hasFailedDeps) | ||||
|             phaseTestGroups.push(...testGroups); | ||||
|         } | ||||
|         if (phaseTestGroups.length) { | ||||
|           await dispatcher.run(phaseTestGroups, extraEnvByProjectId); | ||||
|           await dispatcher.stop(); | ||||
|           for (const [projectId, envProduced] of dispatcher.producedEnvByProjectId()) { | ||||
|             const extraEnv = extraEnvByProjectId.get(projectId) || {}; | ||||
|             extraEnvByProjectId.set(projectId, { ...extraEnv, ...envProduced }); | ||||
|           } | ||||
|         } | ||||
|         if (!failureTracker.hasWorkerErrors()) { | ||||
|           for (const { project, projectSuite } of projects) { | ||||
|             const hasFailedDeps = project.deps.some((p) => !successfulProjects.has(p)); | ||||
|             if (!hasFailedDeps && !projectSuite.allTests().some((test) => !test.ok())) | ||||
|               successfulProjects.add(project); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     teardown: async ({ phases }) => { | ||||
|       for (const { dispatcher } of phases.reverse()) | ||||
|         await dispatcher.stop(); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| function createStartDevServerTask() { | ||||
|   return { | ||||
|     title: "start dev server", | ||||
|     setup: async ({ config }, errors, softErrors) => { | ||||
|       if (config.plugins.some((plugin) => !!plugin.devServerCleanup)) { | ||||
|         errors.push({ message: `DevServer is already running` }); | ||||
|         return; | ||||
|       } | ||||
|       for (const plugin of config.plugins) | ||||
|         plugin.devServerCleanup = await plugin.instance?.startDevServer?.(); | ||||
|       if (!config.plugins.some((plugin) => !!plugin.devServerCleanup)) | ||||
|         errors.push({ message: `DevServer is not available in the package you are using. Did you mean to use component testing?` }); | ||||
|     }, | ||||
|     teardown: async ({ config }) => { | ||||
|       for (const plugin of config.plugins) { | ||||
|         await plugin.devServerCleanup?.(); | ||||
|         plugin.devServerCleanup = void 0; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| // Annotate the CommonJS export names for ESM import in node: | ||||
| 0 && (module.exports = { | ||||
|   TestRun, | ||||
|   createApplyRebaselinesTask, | ||||
|   createClearCacheTask, | ||||
|   createGlobalSetupTasks, | ||||
|   createListFilesTask, | ||||
|   createLoadTask, | ||||
|   createPluginSetupTasks, | ||||
|   createReportBeginTask, | ||||
|   createRunTestsTasks, | ||||
|   createStartDevServerTask, | ||||
|   runTasks, | ||||
|   runTasksDeferCleanup | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone