首页 / 资讯中心 / 文章详情

Macro 团队日历浏览器验证套件:用合成身份端到端校验团队共享、OOO 叠加与点事件渲染

Macro 团队日历浏览器验证套件:用合成身份端到端校验团队共享、OOO 叠加与点事件渲染 ★ FEATURED ARTICLE
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载apps/web/tests/calendar-team是 Macro 前端针对团队日历能力构建的一套浏览器级验证工具。它加载完整的打包应用用合成身份拦截认证、团队与日历 HTTP 流量在真实浏览器中验证团队休假叠加层团队共享策略计入忙碌的日历以及导入点事件的紧凑渲染与仅元数据编辑等行为。读完后你将掌握该套件的隔离机制、可配置环境变量、运行方式以及每一项断言背后的前端源码依据并能在自己的开发环境中复现和扩展这些检查。一、这套工具要解决什么问题团队日历是一类典型的跨账号可见性功能一旦写错用户可能看到同事的日程标题或者在修改一个设置时整页闪烁加载态。传统的单元测试无法覆盖这些组合行为而手工点测又难以回归。该验证套件因此被设计成一个合成环境的端到端脚本真实组件、真实路由、真实渲染管线唯独 API 传输层被完全接管。原文档对该套件的边界给出了非常明确的界定它加载完整打包的应用用合成身份拦截 auth、team、calendar 的 HTTP 流量它阻断非本地请求与 WebSocket 流量它不验证 Rust 服务端也不验证 Google 权限自己日历的读取走既有的 REST 回退路径REST fallback使用实时时钟而不是固定时间在截图前检查加载占位符已经消失。这些约束决定了它的定位这是一个前端行为契约测试而不是集成测试或权限测试。任何涉及服务端授权、Google OAuth scope 或真实同步链路的结论都不能由这套脚本给出。二、运行前的准备与执行步骤套件需要 Chromium并且所有命令都应在apps/web目录下执行原文档已明确这一点。完整流程分三步# 1. 用测试专用 vite 配置构建完整应用 MODEdevelopment bunx vite build -c tests/calendar-team/vite.config.ts # 2. 启动预览服务器固定 127.0.0.1:3037base 为 /app bunx vite preview -c tests/calendar-team/vite.config.ts \ --outDir /tmp/calendar-team-browser-dist --base /app --port 3037# 3. 另开一个终端把浏览器脚本打成 Node 可执行模块并运行 bun build tests/calendar-team/browser.ts --targetnode \ --packagesexternal --outfilenode_modules/.calendar-team-browser/browser.mjs node node_modules/.calendar-team-browser/browser.mjs这里有两个容易被忽略的细节构建输出目录必须与预览目录一致。配置文件里build.outDir已固定为/tmp/calendar-team-browser-distpreview也固定为127.0.0.1:3037且strictPort: true端口被占用时直接失败而不是漂移。命令行再传一次--outDir/--base/--port是显式冗余避免误用其他配置。浏览器脚本要打成一个独立的 Node 入口。browser.ts顶部使用了顶层await通过bun build --targetnode --packagesexternal生成 ESM 文件后由node执行playwright/test等依赖保持在外部不被打进 bundle。环境变量一览变量作用默认/说明CHROMIUM_PATH指定替代的 Chromium 二进制默认/usr/bin/chromium见 browser.tsCALENDAR_TEAM_BROWSER_CDP连接已有浏览器如http://127.0.0.1:9222在其中新建隔离 context只关闭自己创建的 context不动已有会话与标签页CALENDAR_TEAM_BROWSER_URL覆盖预览服务的 origin默认http://127.0.0.1:3037MODE构建模式命令中显式传development原文档特别说明不会修改已有浏览器 context、会话或栈。这在 CI 与本地调试并存时非常关键——脚本不会关闭你正在用的浏览器只会关闭它自己创建的 contextif (!sharedCdp) await browser.close();。截图统一输出到/tmp/calendar-team-*.png网格、OOO 隐藏态、设置页、设置修改后以及最终态各一张calendar-team-grid.png、calendar-team-ooo-hidden.png、calendar-team-settings.png、calendar-team-settings-after.png、calendar-team-final.png。三、隔离机制如何做到完整应用 零真实依赖3.1 构建期用 Vite 配置锁定合成环境vite.config.ts 在加载时直接Object.assign(process.env, {...})把运行环境钉死为一组合成值Object.assign(process.env, { MODE: development, VITE_LOCAL_SERVERS: ALL, VITE_LOCAL_BACKEND_ORIGIN: same-origin, VITE_ENABLE_CALENDAR_UI: true, VITE_ENABLE_CALENDAR_TEAM_SHARING: true, VITE_ENABLE_CALENDAR_TEAM_OOO: true, VITE_ENABLE_GRAPHQL_CALENDAR: false, VITE_DISABLE_BROWSER_TURSO_CACHE: true, VITE_ENABLE_IN_APP_TOURS: false, VITE_ENABLE_BROWSER_OTEL: false, });这组变量的含义可以从前端源码反推VITE_ENABLE_CALENDAR_TEAM_SHARING/VITE_ENABLE_CALENDAR_TEAM_OOO对应 featureFlags.ts 中的enableCalendarTeamSharing与enableCalendarTeamOoo。两者默认值是onInDev开发态默认开启套件里再显式置true避免将来默认值调整导致测试失效。注释同时说明 OOO 是纯增量功能关闭时该区块根本不挂载也不会发起团队休假查询。VITE_ENABLE_GRAPHQL_CALENDARfalse把日历数据固定走 REST呼应文档中own-calendar reads use the existing REST fallback。VITE_DISABLE_BROWSER_TURSO_CACHEtrue关闭本地 Turso 缓存避免浏览器内持久化状态跨运行污染断言。VITE_LOCAL_SERVERSALLVITE_LOCAL_BACKEND_ORIGINsame-origin所有服务客户端都指向当前 origin这样page.route(**/*)才能用一个处理器统一接管。配置通过mergeConfig(createAppViteConfig()(env), {...})合并说明它复用了应用的主 Vite 配置因此测的是真实构建产物而非精简夹具同时把envDir指向测试目录、cacheDir指向node_modules/.vite-calendar-team-browser不会干扰日常开发缓存。3.2 运行期cookie、偏好与网络三层拦截browser.ts 在导航前完成四件事伪造登录 cookielogintrue仅对测试 origin 生效。预置日历偏好通过addInitScript写入macro:pref:calendar:settings包含periodView: timeGridWeek、空的hiddenSourceIds、showWeekends: true、weekStartsOn: 0、timeFormat: 24-hour。这一步让后续隐藏/恢复来源的断言有确定的初始状态。关闭所有 WebSocketpage.routeWebSocket(**/*, (socket) socket.close())。这是阻断 websocket 流量的落地方式——不是让连接失败而是让它立即关闭从而验证界面在无实时通道时的降级表现。阻断非本地请求路由处理器第一行if (url.origin ! origin) return route.abort();与文档中的blocks nonlocal requests一致。此外还挂了三个观测钩子pageerror收集页面异常、requestfailed只记录同源且非net::ERR_ABORTED的失败注释说明导航与刻意的过期检查会取消 fetch、unknown记录所有未被显式 mock 的请求路径作为诊断输出的一部分。3.3 实时时钟与合成日期与许多固定Date的夹具不同这里保留浏览器真实时钟原因写在注释里日历加载过渡会比对流逝时间// Keep the browser clock live: calendar loading transitions compare elapsed time.。但事件数据仍需要稳定的日期锚点因此用当前日期生成fixtureDate并派生出次日nextDate所有事件时间都以${fixtureDate}T${hour}:00:00Z构造时区固定UTC、视口1500×1100。唯一依赖真实时间的是Upcoming断言只有当Date.now()早于事件开始时间时才检查该按钮避免脚本在事件时刻之后运行时出现假失败。四、被拦截的 API 契约路由处理器构成了一份可执行的接口契约理解它等于理解了前端团队日历依赖的数据形状路径方法返回/行为/auth/jwt/refresh任意{}让会话保持已刷新/auth/teamGET团队00000000-...-1111成员含本人、Alice、Bob均为member/free/auth/user/get_names_with_emailGET三个用户的姓名用于getDisplayName渲染 Bob Fixture/calendar/team-sharingGET / PUT读写sharingPUT 会写入mutations[]/calendar/availability-calendarsGET两个日历primary计入、followed不计入/calendar/availability-calendars/:idPATCH/PUT返回 204并更新included与mutations[]/calendar/calendarsGET两个来源同一emailAddress用于账号分组/dss/calendar-events/teamGET固定 404并记录查询串到teamReads[]/dss/calendar-events/team-out-of-officeGET返回 Bob 的全天 OOO 条目/calendar/events/:idPATCH记录eventPatch回写标题/dss/calendar-eventsGET返回点事件ownPointsyncStatus: ready其余未命中的路径交给bootstrapResponses来自 apps/web/tests/native/fixtures/bootstrap.ts兜底仍未命中则记入unknown并返回 404而不是静默通过。注意/dss/calendar-events/team被故意设成 404这不是占位而是断言的一部分——脚本要证明没有任何界面会去读取队友的完整日历。五、断言逐条拆解5.1 团队日历入口必须不存在进入/app/calendar/week后先等待Imported point出现再执行expectRevealedCalendar()await expect(activePage.locator([data-calendar-loading-skeleton])) .toHaveCount(0, { timeout: 15000 }); await expect(activePage.locator( [data-calendar-loading-state]:not([data-calendar-loading-statehidden]) )).toHaveCount(0, { timeout: 15000 });注释点出一个真实陷阱Playwright 的可见性检查接受opacity: 0仅断言 DOM 里有标题可能在 FullCalendar 用骨架遮蔽芯片时依然通过。因此这里改为断言骨架节点数量为 0、加载状态属性不在非 hidden 状态。随后检查侧边栏rolecomplementary名称Calendar navigation存在Calendars标题但页面上不存在Team calendars文本也不存在Show team calendars开关。这与源码一致当前实现是只读的团队投影 休假叠加层并没有整块团队日历开关。5.2 点事件紧凑渲染、单一时间、不可拖拽ownPoint的time通过pointTime()构造endsAt直接复用startsAt即零时长。断言如下芯片高度 0 30注释直白地写着Point must have a compact footprint, not an invented hour防止把点事件渲染成一个小时的块不带fc-event-draggable/fc-event-resizable类文本包含11:00不包含12:00——只能显示一个时间点若当前时间早于事件时刻Open Imported point按钮应可见注释说明Upcoming 使用精确的点时刻绝不当作进行中点击后出现/No duration/提示。5.3 编辑标题必须省略时间字段这是整套测试里最有价值的一条契约打开编辑填入Imported point updated选择Add meeting link → Macro call点击保存断言出现Give this event a duration before adding a Macro call.关键此时assert.deepEqual(mutations, [], Rejected point call must not save calendar changes)——校验失败必须在任何变更发出之前拦截再把链接改回No meeting link并保存断言 PATCH 的eventPatch.title Imported point updated且time in eventPatch false。也就是说编辑一个点事件的标题请求体里不能出现 time 字段否则服务端可能把一个瞬时点补成一个时长区间。5.4 团队休假叠加层隐藏网格、保留列表、持久化侧边栏中的复选框Show team out of office on the calendar与网格上的Bob Fixture: Out of office芯片联动初始两者都可见侧边栏列表行Bob Fixture也在点击Show on calendar后复选框取消、芯片计数为 0但列表行仍然可见localStorage的hiddenSourceIds必须精确等于[team-ooo]再点一次复选框恢复、芯片恢复。这条断言与源码强对应types.ts 定义TEAM_OOO_SOURCE_ID team-ooouse-team-ooo.ts 则用teamOooSourceId(ownerId)生成team-ooo:ownerId作为单个同事的可见性来源 id并把整个 OOO 集合归入sourceCalendarIds: [TEAM_OOO_SOURCE_ID]。因此隐藏作用于展示层来源而不是删除数据——这解释了为什么侧边栏列表还保留那一行。同一文件还揭示了 OOO 映射的关键细节当团队共享策略为busy_only时服务端不下发标题前端回退为Out of office并通过teamProjection.kind区分busy与details标题最终拼成${name}: ${title}即界面上看到的Bob Fixture: Out of office。5.5 设置页共享策略、账号分组与三态复选框导航到/app/settings/calendar后Share with teammates下拉初始显示Busy blocks并展示说明文案Teammates see when you are busy, not what.页面上依然不存在Show team calendars开关定位到包含标题Calendars that count as busy的最内层section注释提示页面存在 section 嵌套所以用.last()账号复选框offlineexample.com处于不确定态indeterminate true因为两个子日历一个计入、一个不计入展开账号后My primary勾选、Subscribed coworker未勾选。这三个选项的文案与 id 直接来自 team-sharing-settings.tsxid名称说明busy_onlyBusy blocksTeammates see when you are busy, not what.allEvent detailsTeammates see your events. Private events stay busy blocks.noneNothingTeammates cannot see your calendar or availability.三态逻辑则由 SourceControls.tsx 提供按账号分组groupCalendarSourcesByAccount、全选/部分选决定checked/indeterminate、以及Expand/Collapse email按钮的无障碍名称。分组数据本身由 availability.ts 的availabilitySources()生成——优先沿用侧边栏已知来源的顺序与颜色未知来源才追加默认颜色这解释了测试里这两个日历为什么能稳定地按账号聚合。5.6 修改设置不能闪出加载态在改动共享策略与忙碌日历之前脚本先注入watchLoadingText()用MutationObserver监听document.body的childList/subtree/characterData只要文本中出现Loading your team…、Loading calendars…、Loading team calendar settings…三者之一就记录一次。随后完成一系列修改共享策略Busy blocks → Event details断言sharing all且说明文案变为Teammates see your events. Private events stay busy blocks.→ Nothing断言sharing none勾选Subscribed coworker断言included.followed true账号复选框随之被勾选点击账号名取消整组断言included.primary false included.followed false账号复选框取消回到Busy blocks断言sharing busy_onlywaitForTimeout(1500)等待保存后的 refetch 落定最后assert.deepEqual(await loadingSeen(), [], Changing a setting must not flash a loading state);这些文案与 team-sharing-settings.tsx 的实际渲染分支一一对应只在teamLoading时显示Loading your team…只在calendarsLoading时显示Loading calendars…calendar-team.tsx里则是Loading team calendar settings…。因此不闪加载等价于修改设置时不应把已加载的查询退回 pending 状态——例如乐观更新或保留既有数据而不是重置整个 suspense 边界。5.7 收尾断言与退出码try/catch/finally结构保证无论成功失败都会输出诊断 JSON{ errors: [], requestFailures: [], unknown: [], teamReads: [], mutations: [] }同时满足三条断言assert.deepEqual(teamReads, [], No surface reads teammate calendars)assert.deepEqual(errors, [])无未捕获页面异常assert.deepEqual(requestFailures, [])同源请求无失败。失败时process.exitCode 1在 CDP 共享模式下finally里用process.exit(...)显式退出避免残留的连接阻止进程结束。关闭 context 前调用page.removeAllListeners(requestfailed)注释说明了原因关闭 context 会取消无关预加载那是清理而非被测行为的失败。六、与单元测试的分工这套浏览器脚本并不孤立存在。它对应的纯逻辑部分有专门的单测use-team-ooo.test.tsx 验证hiddenSourceIds含team-ooo时叠加层被过滤、恢复后重新出现preferences.test.ts 验证hiddenSourceIds: [team-ooo, holidays]的读写merge.test.ts 用team-ooo:bob作为芯片来源team-cache.test.ts 覆盖 OOO 查询键的失效逻辑。分工可以概括为单测锁定数据映射、缓存键与过滤规则浏览器套件锁定DON 是否渲染、点击是否生效、请求是否发出、是否闪烁这类只有真实 DOM 与真实路由才能证明的行为。七、数据加载策略为什么这套前端值得用浏览器测team-ooo.ts 展示了 OOO 数据的加载设计它直接影响浏览器层的可观测行为固定 91 天窗口use-team-ooo.ts中TEAM_OOO_WINDOW_DAYS 91以 2024-01-01 这个固定的本地周一为纪元任何视口都被换算成整窗口请求日历分页与侧边栏因此共享同一批请求而不是各自轮询自己的范围分页与二分拆分单次limit 2000若响应hasMore为真就把范围切成两半递归拉取跨切分点的事件按[eventId, occurrenceKey]去重统一轮询TEAM_OOO_POLL_INTERVAL 30_000用全局定时器 queryClient.invalidateQueries代替 per-query 的refetchInterval避免同一范围被多个 observer 重复拉取窗口隐藏时!focusManager.isFocused()跳过。这些机制意味着测试里只需要 mock 一个/dss/calendar-events/team-out-of-office就能驱动整个叠加层也意味着请求次数本身是可断言的资源——如果将来有人改成按视口逐页请求缓存键与轮询逻辑都会变化而浏览器套件的unknown列表与requestFailures会是第一道警报。八、已知边界与扩展建议必须再次强调原文档明确写下的三条限制避免误用结论不验证 Rust 服务。脚本里/dss/calendar-events/team-out-of-office返回的是手写的bobOutOfOffice服务端的可用性计算、权限判定完全不在覆盖范围内。不验证 Google 权限。设置页文案明确写着 Google Calendar permissions stay the same但脚本并不检查任何 OAuth 授权态。不覆盖真实会话。登录靠 cookie mock 的/auth/jwt/refresh任何依赖真实令牌链路的缺陷都不会暴露。在此前提下套件有几个自然的扩展方向想验证多人叠加可把bobOutOfOffice扩展为数组并同步更新get_names_with_email注意侧边栏列表行与芯片数量的断言需要成对调整想验证Event details策略下标题可见、busy_only下回退为Out of office可让 mock 依据当前sharing变量决定是否返回title——use-team-ooo.ts 正是按item.title undefined决定teamProjection.kind的想扩展成 CI 任务注意strictPort: true与固定/tmp输出目录在并发执行时会冲突需要为每个作业指定独立端口与输出路径排查失败时优先看finally打印的unknown与teamReads前者是漏 mock 的接口后者非空则说明某个界面真的去读了队友日历。九、复现清单若要在本地完整复现一次验证按下面顺序执行任一步失败都不必继续确认apps/web下依赖已安装Chromium 可用或设CHROMIUM_PATHMODEdevelopment bunx vite build -c tests/calendar-team/vite.config.ts产物落在/tmp/calendar-team-browser-distbunx vite preview -c tests/calendar-team/vite.config.ts --outDir /tmp/calendar-team-browser-dist --base /app --port 3037另开终端执行bun build tests/calendar-team/browser.ts --targetnode --packagesexternal --outfilenode_modules/.calendar-team-browser/browser.mjsnode node_modules/.calendar-team-browser/browser.mjs成功时打印PASS: imported point rendering and metadata-only edit, team out-of-office overlay checkbox, no team calendar overlay or controls, team sharing select, account-grouped busy calendars, and settings changes without a loading flash查看/tmp/calendar-team-*.png与终端末尾的 JSON确认errors、requestFailures、teamReads均为空数组。这套工具的价值不在于跑通一次而在于它把若干容易被忽视的产品契约固化成了可执行断言点事件不虚构成一小时、改标题不带时间、拒绝操作不落库、隐藏叠加层不清列表、切设置不闪加载、任何界面都不读队友日历。这些正是团队日历类功能最容易在迭代中悄悄退化的地方。赞分享后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载相关推荐Nuclide折叠状态共享团队代码浏览体验一致化Nuclide折叠状态共享团队代码浏览体验一致化 引言 在团队协作开发过程中代码浏览体验的一致性对于提高工作效率至关重要。Nuclide作为一款基于Atom开发工具agno Team Brain用一个 MCP 端点和 Token 身份构建全团队共享决策日志附测试验证记录与源码解析agno Team Brain用一个 MCP 端点和 Token 身份构建全团队共享决策日志附测试验证记录与源码解析 本文以 cookbook/examp人工智能AI AgentAgent 框架多智能体工具调用RAGAgent 记忆Agent 工作流后端深入 Macro chat crateAI 会话领域模型、Kafka 事件契约与团队共享边界深入 Macro chat crateAI 会话领域模型、Kafka 事件契约与团队共享边界 crates/chat 是 Macro 统一工作空间中负责 AI后端前端协同办公即时通讯企业应用CRMAI Agent人工智能上一篇Flex:ai生产环境部署完整清单从openEuler到K8s集群的12步落地指南下一篇训练策略模型的关键帧技巧IB-Robot 帧检测与训练权重机制详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站