当前位置: 首页 > news >正文

基于Docker与GitHub Actions构建稳定可复现的Playwright自动化测试流水线

1. 项目概述:为什么要把 Playwright 测试塞进 Docker 和 GitHub Actions?

如果你和我一样,负责一个前端或全栈项目的自动化测试,那你肯定对 Playwright 不陌生。这个由微软开源的浏览器自动化工具,凭借其跨浏览器支持、强大的 API 和出色的执行速度,已经成了很多团队的首选。但问题来了:本地跑得飞起的测试脚本,一到同事的机器上就报错,或者每次提交代码后,都得手动去跑一遍回归测试,这太不“现代”了。

这就是我们今天要聊的核心:如何构建一个稳定、可复现、且能自动运行的 Playwright 测试流水线。答案就是标题里的三件套:Playwright、Docker 和 GitHub Actions。简单来说,我们想让测试这件事变得像喝水一样自然——代码一推,测试自动在云端一个干净、一致的环境里跑起来,结果报告自动生成并发布,团队里的每个人都能立刻看到这次提交是“绿灯”还是“红灯”。

这不仅仅是技术上的缝合,更是一种工程实践的提升。Docker 解决了“在我机器上能跑”的经典难题,它把测试运行所需的所有依赖(特定版本的浏览器、系统库、甚至字体)都打包进一个镜像,确保在任何地方执行结果都一致。GitHub Actions 则提供了强大、灵活且免费的自动化平台,让我们可以定义“在什么事件发生时,执行什么操作”。将两者结合,你就拥有了一条从代码提交到测试反馈的全自动流水线。

对于前端、Node.js 后端,或者任何带有 Web 界面的项目来说,这套组合拳的价值巨大。它特别适合追求快速迭代、需要保证核心功能稳定的团队。接下来,我会带你从零开始,拆解每一个环节,分享我趟过的坑和总结的最佳实践,目标是让你能直接复制这套方案,用到自己的项目里。

2. 整体架构与核心思路拆解

在动手写一行配置之前,我们得先想清楚整个流程是怎么运转的。一个健壮的 CI/CD 测试流水线,其核心目标就四个字:可靠反馈。任何导致反馈延迟、失真或失败的因素,我们都要在架构层面尽量避免。

2.1 为什么是 Docker + GitHub Actions?

首先,为什么不直接在 GitHub Actions 的ubuntu-latest虚拟机里安装 Playwright 来跑测试呢?理论上可以,但存在几个痛点:

  1. 依赖安装耗时:每次流水线启动,都需要执行npm installplaywright install,下载node_modules和浏览器二进制文件,这可能会消耗好几分钟。
  2. 环境不一致风险:GitHub Actions 的 Runner 镜像可能会更新,系统库的微小变化有可能影响浏览器或 Playwright 的稳定性。虽然不常见,但一旦发生,排查成本极高。
  3. 缺乏本地一致性:你无法在本地完全模拟 CI 环境进行调试。如果 CI 失败了,你只能在日志里猜,很难在本地复现。

引入 Docker 后,我们将测试环境“固化”了。我们预先构建一个包含了项目代码、所有 Node 依赖、以及 Playwright 所需全部浏览器和系统库的 Docker 镜像。这个镜像是我们定义的“唯一真理源”。无论是在本地开发机、同事的电脑,还是在 GitHub Actions 的云端 Runner 上,只要基于这个镜像运行容器,测试环境就是 100% 一致的。这带来了几个立竿见影的好处:

  • 极快的 CI 启动速度:Runner 只需要拉取我们预先构建好的镜像(如果利用缓存,速度更快),无需再安装任何东西,直接可以执行测试命令。
  • 完美的环境一致性:彻底杜绝了“环境问题”。
  • 便捷的本地调试:当 CI 失败时,你可以在本地用完全相同的镜像启动一个容器,进入内部调试,完美复现问题。

而 GitHub Actions 作为编排者,它的角色是响应事件(如pushpull_request),拉取代码,然后执行我们定义好的“任务”(Jobs)。在我们的架构里,这个任务的核心就是:运行那个包含了所有测试环境的 Docker 容器,并执行测试命令

2.2 核心工作流设计

我们的工作流将包含两个核心阶段,通常设计为两个独立的 Job,以便逻辑清晰且可以并行或设定依赖关系。

  1. 构建与推送 Docker 镜像(Build & Push Image)

    • 触发条件:通常是在代码合并到主分支(如mainmaster),或者为版本发布打标签时。我们不想每次提交都构建镜像,那样太浪费资源。
    • 任务内容:读取项目根目录的Dockerfile,构建一个用于测试的 Docker 镜像。然后,将这个镜像推送到一个容器镜像仓库,比如 Docker Hub 或者 GitHub 自己的 Container Registry (GHCR)。推送时,我们会打上latest标签以及基于提交 SHA 或版本号的唯一标签。
  2. 执行测试并发布报告(Test & Publish Report)

    • 触发条件:更频繁,比如每次push到特性分支,或者发起pull_request时。这是保证每次代码变更都能得到快速反馈的关键。
    • 任务内容: a. 检出(Checkout)最新的代码。 b. 从镜像仓库拉取我们预先构建好的测试镜像(或使用缓存层)。 c. 在容器内运行 Playwright 测试命令(如npm run test:e2e)。 d. 测试完成后,将容器内生成的测试报告(如 HTML、JUnit XML、JSON 等)复制到 Runner 的工作空间。 e. 使用 GitHub Actions 的actions/upload-artifact将报告上传,作为本次工作流的“制品”供下载查看。 f. (可选但推荐)使用一个专门的 Action,如dorny/test-reporter,将 JUnit 格式的测试结果摘要直接展示在 GitHub 的 Pull Request 界面上,或者将 HTML 报告部署到 GitHub Pages 等静态站点,生成一个可公开访问的 URL。

注意:这里有一个关键的决策点——是否在测试 Job 中直接构建镜像?对于小型项目或测试本身很简单的情况,可以直接在测试 Job 里docker build并运行,这样配置简单。但对于依赖较多、构建耗时较长的项目,强烈建议采用上述“分离构建”的策略。它利用了 Docker 的层缓存和镜像仓库,使得测试执行 Job 变得极其快速和稳定,是更专业和可扩展的做法。

3. 实战指南:从零搭建完整流水线

理论说完了,我们开始动手。我会假设你有一个现有的 Node.js 项目,并且已经用 Playwright 写了一些端到端(E2E)测试。

3.1 第一步:准备你的 Playwright 项目

确保你的项目结构清晰,测试命令配置妥当。一个典型的package.json中测试脚本部分可能如下:

{ "scripts": { "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:report": "playwright test --reporter=html,line", "postinstall": "playwright install --with-deps chromium" }, "devDependencies": { "@playwright/test": "^1.40.0" } }

关键点:

  • postinstall钩子:这是一个非常实用的技巧。当在容器内执行npm install后,会自动执行playwright install --with-deps chromium,安装 Playwright 的 CLI 和 Chromium 浏览器及其系统依赖。--with-deps参数至关重要,它会自动安装浏览器运行所需的系统库(如 libglib)。
  • 我们配置了htmlline两种报告器。html报告用于生成丰富的交互式网页报告,line报告则在控制台输出简洁的实时进度。

3.2 第二步:编写 Dockerfile

在项目根目录创建Dockerfile。我们的目标是构建一个最小化、仅用于运行测试的镜像。

# 使用官方 Node.js 运行时作为父镜像 # 选择 Alpine 版本可以极大减小镜像体积,但需注意某些库的兼容性。 # 这里使用 slim 版本,在体积和兼容性间取得平衡。 FROM node:18-slim # 设置工作目录 WORKDIR /usr/src/app # 将 package.json 和 package-lock.json 复制到工作目录 # 先复制依赖定义文件,利用 Docker 缓存层,避免每次代码变更都重新 npm install COPY package*.json ./ # 安装项目依赖 # 使用 ci 命令替代 install,它严格根据 lock 文件安装,更适用于 CI 环境,速度更快、更确定。 RUN npm ci # 将项目所有源代码复制到容器中 COPY . . # 声明容器运行时暴露的端口(如果测试涉及启动本地服务器) # EXPOSE 3000 # 定义默认命令,当容器启动时运行测试 CMD ["npm", "run", "test:e2e:report"]

重要优化与解释

  1. .dockerignore文件:务必在根目录创建.dockerignore,忽略node_modules、测试报告、日志等不必要的文件被复制进镜像,这能显著减少构建上下文大小和镜像层体积。
    node_modules npm-debug.log playwright-report/ test-results/ .git
  2. npm civsnpm install:在 CI 环境中,始终优先使用npm ci。它会先删除现有的node_modules,然后严格按照package-lock.json安装依赖,确保每次安装的结果完全一致。npm install则可能会更新 lock 文件,引入不确定性。
  3. 镜像标签:在实际的 CI 中,我们构建的镜像会带有特定标签,如myapp-test:${GITHUB_SHA},以便追踪。

3.3 第三步:配置 GitHub Actions 工作流

在项目根目录创建.github/workflows/playwright-ci.yml文件。

3.3.1 阶段一:构建与推送镜像 Job

这个 Job 我们命名为build-test-image,它只在向主分支合并或打标签时触发。

name: Playwright CI with Docker on: push: branches: [ main, master ] tags: [ 'v*' ] # 发布版本时也构建镜像 pull_request: branches: [ main, master ] # 也可以添加手动触发 workflow_dispatch: jobs: build-test-image: # 仅当向主分支推送或打标签时运行此Job if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) runs-on: ubuntu-latest permissions: contents: read packages: write # 如果需要推送到 GHCR,需要此权限 steps: - name: Checkout code uses: actions/checkout@v4 - name: Log in to GitHub Container Registry # 使用 GHCR 作为私有镜像仓库,安全且免费。如需使用 Docker Hub,请更换为 docker/login-action uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }}/e2e-test tags: | type=sha,prefix={{branch}}- type=ref,event=tag latest - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha # 使用 GitHub Actions 缓存 cache-to: type=gha,mode=max

关键点解析

  • docker/metadata-action:这个 Action 非常强大,能自动为我们的镜像生成合理的标签。例如,对于一次提交,它会生成类似ghcr.io/yourname/yourrepo/e2e-test:main-a1b2c3dghcr.io/yourname/yourrepo/e2e-test:latest的标签。
  • cache-from/cache-to:配置构建缓存到 GitHub Actions 的缓存服务中,可以极大加速后续的镜像构建过程,特别是npm ci和 Docker 层缓存。
  • secrets.GITHUB_TOKEN:这是 GitHub 自动为每个工作流运行提供的令牌,无需手动配置,用于推送镜像到同仓库的 GHCR。
3.3.2 阶段二:执行测试并发布报告 Job

这个 Job 我们命名为run-tests,在推送代码到任何分支或创建 PR 时都会触发。它依赖于上一阶段构建的镜像。

run-tests: runs-on: ubuntu-latest # 如果需要,可以指定容器运行。但这里我们选择在宿主机运行docker命令,更灵活。 # container: ghcr.io/${{ github.repository }}/e2e-test:latest # 直接使用容器运行Job steps: - name: Checkout code uses: actions/checkout@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Pull test image or build locally id: docker run: | # 尝试拉取为本次提交构建的镜像,如果不存在(例如在PR中,该提交还未触发构建),则回退到拉取最新的镜像,再不行就本地构建(仅作演示,生产环境建议分离) IMAGE_TAG="ghcr.io/${{ github.repository }}/e2e-test:${{ github.sha }}" LATEST_TAG="ghcr.io/${{ github.repository }}/e2e-test:latest" if docker pull $IMAGE_TAG; then echo "image=$IMAGE_TAG" >> $GITHUB_OUTPUT elif docker pull $LATEST_TAG; then echo "image=$LATEST_TAG" >> $GITHUB_OUTPUT echo "Using latest image as fallback" else echo "No pre-built image found. Building locally..." docker build -t local-test-image . echo "image=local-test-image" >> $GITHUB_OUTPUT fi - name: Run Playwright tests in Docker container run: | # 运行容器,将当前代码目录挂载到容器的 /usr/src/app (覆盖镜像内的旧代码) # 注意:这里使用了 `--ipc=host`,这是一个重要的经验参数。 # 在Linux环境下,Chromium可能会因为IPC命名空间问题导致崩溃,此参数可解决。 docker run \ --ipc=host \ --rm \ -v $(pwd):/usr/src/app \ -w /usr/src/app \ ${{ steps.docker.outputs.image }} \ npm run test:e2e:report || true # 即使测试失败,也继续后续步骤以获取报告 # 注意:`-v` 挂载覆盖了镜像中的代码,确保我们测试的是最新检出的代码,而不是构建镜像时的代码。 - name: Upload Playwright HTML report if: always() # 无论测试成功失败,都上传报告 uses: actions/upload-artifact@v4 with: name: playwright-html-report path: playwright-report/ retention-days: 7 # 报告保留天数 - name: Upload test results (JUnit format) if: always() uses: actions/upload-artifact@v4 with: name: playwright-junit-results path: test-results/ # Playwright默认JUnit报告输出目录 retention-days: 7 - name: Publish Test Report to PR if: always() && github.event_name == 'pull_request' uses: dorny/test-reporter@v1 with: name: Playwright E2E Tests path: test-results/*.xml # JUnit XML 报告路径 reporter: jest-junit fail-on-error: false # 仅报告,不因测试失败而让本步骤失败

关键点与避坑指南

  1. --ipc=host参数:这是 Linux Docker 容器中运行 Chromium/Chrome 类浏览器的一个关键参数。如果不加,你可能会遇到浏览器进程莫名崩溃或卡死的情况。它让容器使用宿主机的 IPC 命名空间,解决了共享内存问题。在 macOS 的 Docker Desktop 上通常不需要,但在 Linux CI Runner 上几乎是必须的。
  2. 挂载代码卷-v $(pwd):/usr/src/app这行将 Runner 上的最新代码挂载到容器内,覆盖了镜像构建时打包进去的旧代码。这确保了我们在测试本次提交的代码,而不是镜像构建时的代码。这是“分离构建”模式下的标准操作。
  3. if: always():在上传报告和发布报告的步骤中,我们使用了if: always()。这意味着即使前面的测试步骤失败了,这些步骤依然会执行。你必须能看到测试失败的报告,否则排查问题就无从谈起。
  4. dorny/test-reporter:这个 Action 会将 JUnit 格式的 XML 报告解析,并在 Pull Request 的 Checks 选项卡下生成一个漂亮的测试结果摘要,显示通过数、失败数、跳过数,并可以直接点击查看失败的测试用例详情,体验非常好。

4. 高级配置与优化技巧

基础流程跑通后,我们可以追求更快、更稳定、更易用。

4.1 使用官方 Playwright Docker 镜像

微软提供了官方的mcr.microsoft.com/playwright镜像,它预装了所有浏览器和依赖。我们可以基于此镜像构建,进一步简化Dockerfile,并可能获得更好的性能优化。

# 使用带有 Node 版本的官方 Playwright 镜像 FROM mcr.microsoft.com/playwright:v1.40.0-jammy WORKDIR /usr/src/app COPY package*.json ./ RUN npm ci COPY . . CMD ["npm", "run", "test:e2e:report"]

优势

  • 镜像由 Playwright 团队维护,浏览器兼容性有保障。
  • 通常比从node镜像开始安装playwright更小,因为依赖层经过了优化。
  • 省去了自己处理--with-deps和系统库的麻烦。

注意:官方镜像基于 Ubuntu,如果你需要 Alpine 以追求极致体积,需要注意兼容性,Playwright 对 Alpine 的支持可能不如完整发行版完善。

4.2 并行化测试执行

Playwright 原生支持测试文件的并行执行。你可以在playwright.config.ts中配置workers。在 CI 环境中,可以设置为‘100%’来利用所有 CPU 核心。

// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ // 使用所有可用的CPU核心 workers: process.env.CI ? '100%' : undefined, // 或者指定一个固定数值 // workers: process.env.CI ? 4 : undefined, // 设置全局超时 timeout: process.env.CI ? 60000 : 30000, });

在 GitHub Actions 的run-testsJob 中,你也可以通过矩阵策略(strategy.matrix)来并行运行不同浏览器或不同测试套件,但这需要更精细的测试架构设计。

4.3 测试报告的艺术

除了基础的 HTML 和 JUnit 报告,你还可以:

  • Allure 报告:安装allure-playwright报告器,可以生成非常专业、美观的 Allure 报告,并集成到 CI 中。
  • 自定义报告站点:将playwright-report目录(HTML 报告)通过actions/upload-pages-artifactactions/deploy-pages自动部署到 GitHub Pages。这样每次 CI 运行都会生成一个带有唯一 URL 的在线报告,团队成员无需下载压缩包即可查看。
  • Slack/Teams 通知:在工作流末尾添加步骤,使用8398a7/action-slack等 Action,将测试结果(通过/失败、报告链接)发送到团队聊天工具。

4.4 资源清理与成本控制

  • 镜像标签策略:定期清理旧的 Docker 镜像。可以为 GHCR 配置保留策略(如只保留最近 10 个标签的镜像),也可以在工作流中添加一个清理 Job,使用 Docker API 删除超过一定天数的镜像。
  • Artifact 保留:在actions/upload-artifact中设置retention-days,避免测试报告等制品无限期占用存储空间。
  • 使用更小的 Runner:如果测试不重,尝试使用runs-on: ubuntu-22.04或 GitHub 提供的其他尺寸的 Runner,可能成本更低(对于私有仓库)。

5. 常见问题排查与实战心得

这条路我走过,坑也踩过不少。下面是一些你很可能遇到的情况和解决办法。

5.1 浏览器启动失败或崩溃

  • 症状:测试日志显示Browser closed unexpectedlyTarget closed,或者进程无响应超时。
  • 排查
    1. 首要检查--ipc=host:确保在docker run命令中加上了这个参数。这是 Linux 环境下最常见的原因。
    2. 检查共享内存大小:Docker 默认的/dev/shm大小为 64MB,对于 Chrome 可能不够。可以尝试增加:--shm-size=2gb
    3. 使用无头模式:在 CI 中务必使用无头模式(headless: true,这是 Playwright 的默认值)。图形界面在无显示的容器中会导致问题。
    4. 查看 Docker 日志:如果容器完全启动失败,用docker logs <container_id>查看启动日志。

5.2 测试在 CI 中慢得出奇

  • 可能原因
    1. 没有使用缓存:每次都在重新安装node_modules和浏览器。确保利用了 Docker 层缓存(Dockerfile顺序正确)和 GitHub Actions 的cachecache-from
    2. 网络问题:从 npm 或 Docker 仓库拉取资源慢。考虑配置国内镜像源,或者确保使用的镜像仓库(如 GHCR)与 Runner 地域接近。
    3. 测试本身有等待:检查测试代码中是否有不必要的page.waitForTimeout(5000)之类的固定等待,应替换为page.waitForSelectorpage.waitForFunction等条件等待。
    4. 并行度不够:检查playwright.config.ts中的workers设置,在 CI 中应设置为大于 1 的值或‘100%’

5.3 在 PR 中看不到测试报告摘要

  • 检查步骤
    1. 确保生成 JUnit 格式的报告。在 Playwright 配置中启用它:reporter: [ ['html'], ['junit', { outputFile: 'test-results/results.xml' }] ]
    2. 确保dorny/test-reporter步骤的path配置正确指向了生成的 XML 文件。
    3. 检查该步骤的if条件,确保在 PR 事件下会运行。
    4. 去 PR 的 Checks 区域查看,报告通常在那里,而不是在 Files changed 或 Conversation 标签页。

5.4 镜像构建时间过长

  • 优化策略
    1. 利用多阶段构建:如果项目需要编译(如 TypeScript),可以使用多阶段构建,最终只将运行所需的文件(编译后的 JS、node_modules)复制到一个小体积的运行时镜像中。
    2. 使用更小的基础镜像:如node:18-alpine,但要充分测试 Playwright 兼容性。
    3. 精细化 .dockerignore:确保不把playwright-report/.git/、日志等文件加入构建上下文。
    4. 使用 BuildKit 和缓存:GitHub Actions 的docker/build-push-action默认使用 BuildKit,配合cache-from可以极大提升重构建速度。

5.5 本地与 CI 行为不一致

  • 黄金法则:当出现不一致时,第一反应应该是在本地用完全相同的 Docker 命令和镜像复现
    # 在本地终端,使用 CI 中完全相同的镜像和命令 docker run --ipc=host --rm -v $(pwd):/usr/src/app -w /usr/src/app ghcr.io/your-org/your-repo/e2e-test:latest npm run test
    如果本地 Docker 运行通过而 CI 失败,那可能是 CI Runner 的资源限制(CPU、内存)问题。如果本地 Docker 也失败,那恭喜你,问题被成功定位到了容器环境内,排除了宿主机环境的干扰,接下来就可以专注于调试容器内的测试逻辑了。

最后,我想分享一个最深的体会:CI/CD 流水线的价值在于快速反馈,而不是追求 100% 的通过率。一开始,你可能会被一些脆弱的测试(Flaky Tests)所困扰,它们时好时坏。与其花大量时间让所有测试在 CI 里都变绿,不如先确保核心流程的测试稳定,并为脆弱的测试打上标签(如@flaky),在 CI 中跳过或重试它们,同时记录问题并后续优化。先让流水线跑起来,再让它跑得又稳又快。这套基于 Docker 和 GitHub Actions 的 Playwright 测试方案,就是你实现这一目标的坚实起点。

http://www.cnnetsun.cn/news/3747777.html

相关文章:

  • C#配置文件App.config与.settings详解:原理、区别与应用场景
  • 不依赖高价投流,企业如何用BBWEYY搭建长期获客系统,含零代码SAAS、AI编程、源码定制交付
  • 终极指南:如何在Windows上快速安装苹果设备驱动程序解决USB网络共享问题
  • AIGC检测到底在检测什么?2026年论文查重与查AI区别详解
  • 别再堆砌形容词!AI视频提示词的熵减法则:用信息论重构提示结构,3天掌握专业级可控生成
  • ClickUp Brain语音生成功能:项目管理中的TTS应用实践
  • CoolProp免费开源热力学物性计算库:从入门到精通的完整指南
  • STM32 SPI从机配置实战:基于HAL库的中断驱动通信详解
  • ESP32实现轻量级DNS服务器与NCSI欺骗技术
  • SpringBoot+Vue构建高并发在线招投标系统实践
  • WD5105,90V转40V,4A,纯低压适配、极简外围、超低纹波、高性价比量产
  • 解析.NET集合修改异常:InvalidOperationException解决方案
  • 终极模组管理神器:Nexus Mods App 完全使用指南
  • 如何高效管理虚幻引擎资源:UnrealPakViewer深度解析与实战指南
  • bitbrick_k1集群部署prima_cpp实现分布式大模型推理
  • 终极指南:如何用Hotkey Detective快速找出Windows热键冲突元凶
  • 小白程序员必看:收藏这份大模型记忆术,让你的Agent从“一次性工具”变身“长期协作者”!
  • 从“有证书就行”到“四步验证体系”:买非标品之前,我建议你先建立一套自己的选品机制
  • 的Java应用部署。Panel的容器化技术解决了环境配置复杂、版本冲突等问题,提供图形化界面和集中式日志管理,显著提升部署效率... ...
  • 西门子S7-1500与科尔摩根AKD PROFINET通讯实战指南
  • StreamFX插件实战:5个突破性特效方案解决专业直播的核心挑战
  • git基本使用
  • 高频KV Cache读写进行了端到端时序优化
  • Sunshine游戏串流:打造你的个人云游戏服务器终极指南
  • GetQzonehistory终极指南:轻松备份你的QQ空间数字记忆
  • Pandas DataFrame与Python字典互转:核心参数、场景与性能优化
  • RAG技术解析:大模型应用的核心架构与实践
  • AI搜索营销:从关键词匹配到价值深耕的范式转移
  • QtScrcpy技术深度解析:Android设备跨平台屏幕镜像与控制实战指南
  • 2026年做线上商城哪家好?小程序商城、独立站与开发路线对比