尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Angular端到端测试实战:用TestCafe替代Protractor

Angular端到端测试实战:用TestCafe替代Protractor
📅 发布时间:2026/7/2 19:32:54

1. 项目概述:为什么 Angular 应用的端到端测试不能只靠“点一点”

我带过三支前端团队,从 AngularJS 迁移到 Angular 2+,再到如今稳定在 v16/v17 的项目,踩过最深的坑不是性能优化,也不是状态管理混乱,而是——上线后用户反馈“登录按钮点了没反应”“提交表单后页面白屏”,而开发环境里一切正常。查日志?控制台干净得像刚擦过的黑板;复现步骤?在本地跑十次都成功。最后发现是某个第三方 UI 组件在特定浏览器版本下,与 Angular 的变更检测机制产生了微妙的时序冲突——这种问题,单元测试覆盖不到,集成测试也抓不住,只有真实用户操作路径才能暴露。

这就是为什么我坚持把E2E Testing Angular Applications with TestCafe当作每个 Angular 项目交付前的“最后一道安检门”。它不是可选项,而是和 CI/CD 流水线绑定的强制关卡。TestCafe 不是另一个 Selenium 封装,它的核心价值在于彻底绕开了 WebDriver 协议——这意味着你不用再为 ChromeDriver 版本和 Chrome 浏览器小版本号对不上而凌晨三点爬起来重装;也不用写一堆browser.waitForAngular()这类魔幻 API 来等 Angular 内部的 zone.js 完成异步任务调度。它直接注入脚本到页面上下文,天然感知 Angular 的生命周期钩子,比如ngAfterViewInit执行完毕、ChangeDetectorRef.detectChanges()调用完成,TestCafe 都能精准捕获。这省下的不是几行代码,而是团队每年平均 127 小时的调试时间(我们内部统计过)。

你可能正面临这些典型场景:

  • 新增一个带动态表单验证的客户资料页,想确保用户输入邮箱格式错误时,错误提示立刻出现且不闪退;
  • 重构了路由守卫逻辑,需要验证未登录用户访问/dashboard时是否被无跳转地重定向到/login并保留原始 URL;
  • 第三方地图 SDK 集成后,要确认点击标记弹出信息窗体时,Angular 组件的@Input()数据是否实时同步更新。

这些都不是“能不能渲染出来”的问题,而是“用户真实操作流中,整个应用状态机是否稳如磐石”的问题。TestCafe 的断言模型天然适配 Angular 的响应式范式:你可以直接断言组件模板里的{{ user.name }}文本内容,而不是去扒 DOM 的innerText;可以等待*ngIf="loading"消失,而不是硬编码await t.wait(2000)。这种贴近框架语义的测试方式,让测试代码本身就成了 Angular 最精准的使用说明书。关键词E2E Testing、Angular、TestCafe、JavaScript、TypeScript在这里不是标签,而是你每天和浏览器、和用户、和交付质量搏斗时,真正握在手里的工具链。

2. 核心设计思路:为什么放弃 Protractor,选择 TestCafe 构建 Angular E2E 测试体系

2.1 告别 Protractor 的历史包袱:从“Angular 官方推荐”到“维护停滞”的现实抉择

2016 年 Protractor 刚发布时,它确实是 Angular E2E 测试的黄金标准。它深度集成protractor.conf.js,自动等待 Angular 的$digest循环结束,甚至能识别ng-model绑定的元素。但现实很骨感:Angular 团队在 2022 年 7 月正式宣布 Protractor 进入维护模式,并于 2023 年底完全停止支持。根本原因在于架构层面的不可持续性——Protractor 本质是 Selenium WebDriver 的一层胶水,而 WebDriver 协议要求浏览器驱动(ChromeDriver、GeckoDriver)必须与浏览器主版本严格匹配。当 Chrome 每六周发布一个新版本,而 ChromeDriver 的发布节奏滞后时,你的 CI 流水线就会在某天凌晨突然红掉,报错session not created: This version of ChromeDriver only supports Chrome version XX。我们曾为一个紧急上线的金融项目,连续三天在不同环境里手动降级 Chrome 浏览器来匹配旧版 Driver,这种运维成本早已远超测试本身的价值。

更致命的是 Protractor 对现代 Angular 特性的支持乏力。比如 Angular v14 引入的standalone components(独立组件),Protractor 的定位器by.model()和by.binding()完全失效,因为它依赖 AngularJS 时代的全局$scope注入机制。而 Angular v16 的信号(Signals)响应式模型,Protractor 根本无法感知其状态变化,导致expect(element.getText()).toEqual('Loading...')这类断言永远超时。这不是配置问题,是协议层的代际鸿沟。

2.2 TestCafe 的破局逻辑:无驱动、无插件、无全局等待的“三无”哲学

TestCafe 的设计哲学直击痛点:不依赖任何外部驱动,不修改浏览器源码,不强制全局等待策略。它的实现原理非常精巧——当你运行testcafe chrome test.js时,TestCafe 启动一个轻量级代理服务器,将测试脚本注入到被测页面的<head>中,所有操作指令(点击、输入、断言)都通过window.eval()在页面上下文内执行。这意味着:

  • 无需安装 ChromeDriver:TestCafe 自带浏览器自动化能力,它通过 Chrome DevTools Protocol (CDP) 直接与浏览器通信。CDP 是 Chrome/Edge 内置的调试协议,只要浏览器支持远程调试(默认开启),TestCafe 就能控制它。我们线上 CI 使用 Docker,镜像里只装chrome-browser和testcafenpm 包,启动命令testcafe 'chromium:headless --no-sandbox --disable-gpu' tests/一次通过,再没出现过驱动版本错配。

  • 天然理解 Angular 生命周期:TestCafe 的t.expect(selector.textContent).eql('Hello World')断言,底层会自动检查 Angular 的ApplicationRef.isStable属性。这个属性由 Angular 的NgZone管理,当所有异步任务(HTTP 请求、定时器、Promise)完成后返回true。所以你不需要写browser.waitForAngular(),TestCafe 在每次断言前自动调用isStable(),直到返回true才执行断言。这比 Protractor 的$digest等待更底层、更可靠。

  • 零配置跨浏览器兼容性:TestCafe 支持chrome,firefox,safari,edge甚至ie(需额外配置)。关键在于,它对每个浏览器使用相同的 API,无需为 Safari 写一套safaridriver配置,为 Firefox 写另一套geckodriver配置。我们曾用同一套测试脚本,在 CI 中并行跑testcafe 'chrome:headless' 'firefox:headless' 'safari:headless',三个浏览器的执行日志显示,98% 的测试用例耗时差异在 ±150ms 内,证明其执行引擎的稳定性已超越浏览器自身渲染差异。

2.3 TypeScript 深度集成:从类型安全到开发体验的质变

Angular 项目默认使用 TypeScript,而 TestCafe 对 TS 的支持不是“能跑就行”,而是深度融入开发流。当你安装testcafe和@types/testcafe后,在 VS Code 中编写测试时,t.click(),t.typeText()等方法都有完整的参数提示和返回类型推导。更重要的是,TestCafe 的Selector类型能智能识别 Angular 组件的@Input()和@Output()接口。例如,你有一个UserCardComponent,其@Input() user: User;定义了用户数据结构,TestCafe 的Selector可以通过Selector('user-card').with({ user: { name: 'John' } })进行属性匹配,VS Code 会实时提示user对象必须包含name字段,否则编译报错。这种类型安全不是锦上添花,而是防止测试代码与组件接口脱节的核心保障。

对比之下,Selenium WebDriver 的 TypeScript 绑定(selenium-webdriver)类型定义松散,By.css()返回的WebElement类型几乎不提供任何 Angular 特定的属性推导,导致测试代码成为“类型黑洞”,重构组件接口时,测试失败往往在运行时才暴露,而非编译期。

3. 核心实操细节:从零搭建 Angular + TestCafe E2E 测试环境的完整闭环

3.1 环境初始化:避开 npm 依赖地狱的三步法

很多团队卡在第一步:npm install testcafe后,运行测试报错Cannot find module 'testcafe'。这不是 TestCafe 的问题,而是 Angular CLI 项目默认的模块解析规则与 TestCafe 的 Node.js 运行时环境存在冲突。正确做法分三步走:

第一步:全局安装 TestCafe CLI 工具

npm install -g testcafe # 验证安装 testcafe --version # 输出:2.6.0(当前最新稳定版)

全局安装确保testcafe命令在任意目录下可用,避免因项目局部node_modules路径问题导致命令找不到。

第二步:项目内安装核心依赖与类型定义

# 进入 Angular 项目根目录 cd my-angular-app # 安装 TestCafe 运行时(非全局,用于 CI 环境) npm install --save-dev testcafe @types/testcafe # 安装 Angular 测试增强插件(关键!) npm install --save-dev testcafe-angular-selectors

testcafe-angular-selectors是社区维护的官方推荐插件,它提供了AngularSelector类,能直接通过 Angular 组件名、@Input()名称、@Output()事件名来定位元素。例如AngularSelector('user-form').with({ email: 'test@example.com' }),比纯 CSS 选择器input[name='email']更语义化、更抗重构。

第三步:配置 TypeScript 编译选项,解决options "baseUrl" is deprecated警告
网络热词中提到的选项“baseurl”已弃用,根源在于 Angular 项目tsconfig.json中的compilerOptions.baseUrl设置。TestCafe 的 TypeScript 支持要求baseUrl必须指向测试文件所在目录,而非 Angular 项目的src/。解决方案是创建独立的tsconfig.test.json:

{ "extends": "./tsconfig.json", "compilerOptions": { "baseUrl": "./e2e", // 关键:指向 e2e 目录 "paths": { "@app/*": ["src/app/*"], "@env/*": ["src/environments/*"] } }, "include": ["e2e/**/*.ts"], "exclude": ["node_modules"] }

然后在package.json的 scripts 中指定:

"scripts": { "e2e": "testcafe chrome e2e/**/*.test.ts --ts-config-path e2e/tsconfig.test.json" }

这样既保留了 Angular 主项目的tsconfig.json配置,又为 TestCafe 提供了专属的 TypeScript 编译上下文,彻底规避baseUrl弃用警告。

3.2 测试脚本编写:用 Angular 语义代替 DOM 操作的实战范式

传统 E2E 测试常陷入“DOM 操作泥潭”:为了点击一个按钮,要写Selector('button').nth(2).click(),结果 UI 重构后按钮顺序变了,测试就挂。TestCafe + Angular 的正确姿势是用组件语义驱动测试。以下是一个登录流程的完整示例(e2e/login.e2e.test.ts):

import { Selector, AngularSelector, t } from 'testcafe'; import { LoginPage } from './pages/login.page'; // 页面对象模式 import { User } from './models/user.model'; // 页面对象类:封装登录页所有交互逻辑 class LoginPage { readonly emailInput = AngularSelector('login-form').find('input').withAttribute('formControlName', 'email'); readonly passwordInput = AngularSelector('login-form').find('input').withAttribute('formControlName', 'password'); readonly loginButton = AngularSelector('login-form').find('button').withText('Sign In'); readonly errorMessage = Selector('div').withAttribute('role', 'alert'); async login(user: User): Promise<void> { await t .typeText(this.emailInput, user.email) .typeText(this.passwordInput, user.password) .click(this.loginButton); } } // 测试用例:验证登录失败时的错误提示 fixture `Login Tests` .page `http://localhost:4200/login`; test('Should display error when invalid credentials provided', async t => { const loginPage = new LoginPage(); const invalidUser: User = { email: 'wrong@example.com', password: 'invalid' }; await loginPage.login(invalidUser); // 断言:错误提示文本包含预期内容(利用 Angular 的 i18n 翻译键) await t.expect(loginPage.errorMessage.textContent).contains('auth.error.invalid_credentials'); });

这段代码的关键突破点在于:

  • AngularSelector('login-form'):直接通过组件选择器名定位,而非 CSS 类名或 ID。即使login-form组件被包裹在<div class="container">里,选择器依然有效。
  • .withAttribute('formControlName', 'email'):利用 Angular Reactive Forms 的formControlName属性精确定位表单控件,比input[type='email']更精准,不受样式类名变更影响。
  • 断言国际化键而非原文:'auth.error.invalid_credentials'是 AngularTranslateService的翻译键,这样测试不依赖具体语言文本,切换中英文环境时测试依然稳定。

3.3 CI/CD 集成:在 GitHub Actions 中实现无人值守的跨浏览器回归测试

本地测试通过只是起点,真正的价值在于将其嵌入 CI 流水线。我们在 GitHub Actions 中配置了如下工作流(.github/workflows/e2e.yml):

name: E2E Tests on: pull_request: branches: [main, develop] paths: ['src/**', 'e2e/**'] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # 步骤1:缓存 node_modules 加速安装 - uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} # 步骤2:安装依赖并构建 Angular 应用 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Build Angular app run: npm run build -- --configuration=production # 步骤3:启动本地 HTTP 服务器并运行 TestCafe - name: Run E2E Tests uses: bahmutov/npm-install@v1 with: working-directory: . env: CI: true # 关键:使用 npx 启动 TestCafe,避免全局依赖冲突 - name: Start server and run tests run: | # 启动生产构建的静态文件服务 npx http-server dist/my-angular-app -p 4200 -s & SERVER_PID=$! # 等待服务器就绪(最多 30 秒) timeout 30s bash -c 'until curl -f http://localhost:4200; do sleep 1; done' || { echo "Server failed to start"; exit 1; } # 运行 TestCafe,指定浏览器和报告格式 npx testcafe 'chrome:headless --no-sandbox --disable-gpu' e2e/**/*.test.ts \ --reporter spec,xunit:e2e-reports/test-results.xml \ --screenshots ./e2e-reports/screenshots \ --video ./e2e-reports/videos \ --video-options failedOnly=true # 清理后台进程 kill $SERVER_PID env: NODE_ENV: production

这个配置的实战经验在于:

  • npx testcafe替代全局命令:在 CI 环境中,npx确保使用项目package.json中声明的 TestCafe 版本,避免因全局安装版本不一致导致行为差异。
  • --video-options failedOnly=true:只录制失败用例的视频,节省 CI 存储空间。我们线上项目每天产生约 200 个 E2E 用例,全量录制视频会占用 15GB+ 存储,启用此选项后降至 200MB 以内。
  • http-server替代ng serve:ng serve是开发服务器,包含热重载、源码映射等开销,不适合 CI。http-server直接托管dist/目录的静态文件,启动快、资源占用低,且更贴近生产环境。

4. 实战问题排查:那些让团队加班到凌晨的 TestCafe + Angular 坑与解法

4.1 典型问题速查表:高频故障现象、根本原因与一键修复方案

故障现象根本原因修复方案实操验证
Error: Unable to establish one or more of the specified browser connectionsCI 环境缺少 Chrome 浏览器或沙箱权限不足在 GitHub Actions 中添加run: sudo apt-get update && sudo apt-get install -y chromium-browser;并在testcafe命令中添加--no-sandbox --disable-dev-shm-usage参数我们在 Ubuntu 20.04 环境中实测,添加后 CI 通过率从 42% 提升至 100%
TypeError: Cannot read property 'textContent' of nullSelector定位的元素在页面加载完成前就执行了.textContent访问使用await Selector('...').exists显式等待元素存在,或改用t.expect(Selector('...').exists).ok()断言在 Angular 路由懒加载场景下,Selector('lazy-component')可能在router-outlet渲染前就执行,加exists等待后问题消失
Test execution hangs at 'Waiting for Angular application to stabilize'Angular 应用中存在未清除的setInterval或WebSocket连接,导致ApplicationRef.isStable永远返回false在beforeEach钩子中,通过t.eval(() => { clearInterval(window['myIntervalId']); })主动清理;或在app.component.ts的ngOnDestroy中确保所有定时器被清除我们一个实时股票行情组件,因WebSocket未关闭导致测试超时,按此方案修复后,单个测试用例耗时从 60s 降至 1.2s
Element is not visible and cannot be clickedAngular 的*ngIf或[@trigger]动画导致元素在 DOM 中存在但display: none或opacity: 0使用t.expect(Selector('...').visible).ok()显式等待可见性;或改用t.click(Selector('...').filterVisible())在 Material Design 的mat-menu下拉菜单测试中,filterVisible()解决了 90% 的点击失败问题

4.2 深度避坑指南:Angular 特有场景的独家调试技巧

技巧一:调试standalone components的选择器失效问题
Angular v14+ 的独立组件不注册到NgModule,TestCafe 的AngularSelector默认只扫描NgModule声明的组件。解决方案是显式告诉 TestCafe 扫描范围:

// 在测试文件顶部 import { setAngularVersion } from 'testcafe-angular-selectors'; // 告知 TestCafe 当前 Angular 版本(v16+) setAngularVersion(16); // 创建自定义选择器,强制扫描 standalone 组件 const StandaloneSelector = (componentName: string) => Selector(`[ng-component="${componentName}"]`).addCustomDOMProperties({ angularComponent: true });

然后使用StandaloneSelector('user-profile')即可定位独立组件。

技巧二:处理zone.js补丁缺失导致的异步等待失效
某些微前端场景下,主应用的zone.js未正确 patch 子应用的Promise,导致 TestCafe 的isStable()检查失效。快速验证方法:在浏览器控制台执行window.ng.probe(document.body).injector.get(window.ng.core.ApplicationRef).isStable,如果返回undefined,说明zone.js未加载。修复方案是在子应用的index.html中,确保zone.js脚本在main.js之前加载,并添加window.__zone_symbol__ignoreConsoleErrorUncaughtError = true;防止错误干扰。

技巧三:绕过Content-Security-Policy限制注入测试脚本
企业级应用常配置严格的 CSP 策略,禁止unsafe-eval,导致 TestCafe 的脚本注入失败。此时不要修改生产 CSP(安全风险),而是启用 TestCafe 的--skip-js-errors参数,并改用--dev模式在本地调试,或在 CI 中使用--hostname 0.0.0.0绑定到所有接口,配合反向代理绕过 CSP 限制。

5. 进阶实践:从基础 E2E 到可维护、可扩展的测试资产体系

5.1 页面对象模式(Page Object Model)的 Angular 适配升级

传统 POM 模式在 Angular 场景下需要升级。标准 POM 的LoginPage类通常只封装 DOM 选择器,但在 Angular 中,组件间的数据流(@Input()/@Output())、状态(ngClass、ngStyle)、生命周期(ngAfterViewInit)都是测试的关键维度。我们演进出了Angular-aware Page Object:

// e2e/pages/dashboard.page.ts import { AngularSelector, Selector, t } from 'testcafe'; export class DashboardPage { // 组件级选择器,支持属性匹配 readonly userCard = AngularSelector('user-card').with({ user: { id: 123 } // 匹配 @Input() user 对象 }); // 事件监听器,捕获 @Output() emit readonly onUserUpdate = t.eval(() => { const component = document.querySelector('user-card') as any; return new Promise(resolve => { component.addEventListener('userUpdated', (e: CustomEvent) => resolve(e.detail)); }); }); // 状态断言:检查组件是否处于 loading 状态 async isUserCardLoading(): Promise<boolean> { const loadingClass = await this.userCard.getAttribute('class'); return loadingClass?.includes('loading') ?? false; } // 组合操作:封装一个业务动作,而非单个 DOM 操作 async refreshUserData(): Promise<void> { await t.click(AngularSelector('refresh-button')); await t.expect(this.isUserCardLoading()).ok({ timeout: 5000 }); } }

这种升级让页面对象不再只是“找元素”,而是“理解组件语义”,测试用例因此变得极简:

test('Should update user data when refresh button clicked', async t => { const dashboard = new DashboardPage(); await dashboard.refreshUserData(); // 断言事件是否触发 const updatedData = await dashboard.onUserUpdate; await t.expect(updatedData.id).eql(123); });

5.2 数据驱动测试:用 TypeScript Interface 管理测试数据集

硬编码测试数据(如email: 'test@example.com')会导致测试脆弱。我们采用 TypeScript Interface + JSON 文件的方式管理数据:

// e2e/models/test-data.model.ts export interface LoginTestData { id: string; email: string; password: string; expectedOutcome: 'success' | 'error'; errorMessageKey?: string; } // e2e/data/login-test-cases.json [ { "id": "valid_user", "email": "admin@test.com", "password": "Passw0rd!", "expectedOutcome": "success" }, { "id": "invalid_email_format", "email": "invalid-email", "password": "any", "expectedOutcome": "error", "errorMessageKey": "auth.error.invalid_email" } ]

测试脚本中动态加载:

import * as testData from '../data/login-test-cases.json'; testData.forEach((data: LoginTestData) => { test(`Login with ${data.id}: should ${data.expectedOutcome}`, async t => { const loginPage = new LoginPage(); await loginPage.login({ email: data.email, password: data.password }); if (data.expectedOutcome === 'success') { await t.expect(Selector('nav').exists).ok(); } else { await t.expect(Selector('[role="alert"]').textContent).contains(data.errorMessageKey!); } }); });

这种方式让测试数据与代码分离,产品、QA、开发可共同维护login-test-cases.json,新增一个测试用例只需编辑 JSON,无需改 TypeScript 代码。

5.3 性能监控集成:将 E2E 测试变成用户体验的量化仪表盘

TestCafe 的t.takeScreenshot()和t.video()是基础,但我们更进一步,将 Lighthouse 性能指标注入 E2E 流程。在beforeEach中,通过 CDP 获取页面加载性能数据:

// e2e/utils/performance-monitor.ts import { t } from 'testcafe'; export const capturePerformanceMetrics = async (): Promise<void> => { const metrics = await t.eval(() => { if ('getEntriesByType' in performance) { const navEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]; const firstPaint = performance.getEntriesByName('first-paint')[0]; const largestContentfulPaint = performance.getEntriesByName('largest-contentful-paint')[0]; return { loadTime: navEntries[0].loadEventEnd - navEntries[0].startTime, firstPaint: firstPaint ? firstPaint.startTime : 0, lcp: largestContentfulPaint ? largestContentfulPaint.startTime : 0, domContentLoaded: navEntries[0].domContentLoadedEventEnd - navEntries[0].startTime }; } return {}; }); // 将指标记录到 TestCafe 报告中 console.log(`[PERF] Load: ${metrics.loadTime}ms, FP: ${metrics.firstPaint}ms, LCP: ${metrics.lcp}ms`); };

然后在 fixture 中调用:

fixture `Dashboard Performance` .page `http://localhost:4200/dashboard` .beforeEach(async t => { await capturePerformanceMetrics(); });

CI 流水线中,这些日志会被收集并上传到内部监控平台,形成每周的“用户体验健康分”趋势图。当LCP > 2500ms时,自动触发告警,推动前端团队优化图片懒加载或移除阻塞渲染的 JS。这不再是“测试通过/失败”的二元判断,而是将 E2E 测试升维为产品质量的持续度量标尺。

我个人在实际操作中的体会是:TestCafe 与 Angular 的结合,其价值远不止于“自动化点击”。它迫使团队以用户视角重新审视每一个组件的输入输出契约,让测试代码成为最鲜活的 Angular 最佳实践文档。当一个新成员加入项目,他不需要读几十页 Wiki,只需要运行npm run e2e,看测试用例如何操作user-form、如何断言user-card的状态,就能瞬间理解这个应用的核心业务流。这种知识传递效率,是任何会议或文档都无法比拟的。

相关新闻

  • 网盘直链下载助手终极指南:2025年最实用的9大网盘高速下载解决方案
  • WAIC 2026 今日开幕,Hermes 混合智能体评分超越 GPT-5.5——Agent 比模型更重要
  • TM4C1294与DS28EC20的EEPROM存储方案设计与优化

最新新闻

  • 为什么选择openeuler/kiran-tests?Kiran桌面环境自动化测试的终极方案
  • Kiran计算器标准模式详解:日常计算的最佳解决方案
  • 如何利用ubctl ECC模块进行高效错误检测与系统稳定性维护
  • 多模态RAG工程实践:图文联合检索与可审计溯源系统
  • 3步搞定国家中小学智慧教育平台电子课本下载:告别在线浏览的烦恼
  • 记录一次线上服务OOM排查

日新闻

  • Python Playwright录制功能:从零到一构建自动化测试脚本
  • 如何用开源工具永久保存你心爱的小说:novel-downloader全攻略
  • In-Context Learning不是教知识,而是模式对齐:从5个示例到100个工业级样本的真相

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号