CI/CD 已经获得了很大的吸引力,并且可能是 DevOps 新手最常谈论的话题之一。随着市场上可用的 CI/CD 工具的可用性,配置和操作 CI/CD 管道已变得比 5-6 年前容易得多。那时还没有容器,唯一主导该领域的 CI/CD 工具是 Jenkins。 Jenkins 为您提供了一个任务运行器,因此您可以将您的作业定义为顺序或并行运行。

今天,情况有所不同。与 Jenkins 相比,我们在市场上有许多可用的 CI/CD 工具,它们为我们提供了更多的特性和功能。 GitLab CI 就是这样一个著名的 CI/CD 工具,这正是我们将在本文中介绍的内容。

在本文中,我们将使用 GitLab CI/CD 配置一个 CI/CD 管道,并通过 LambdaTest(一个包含 2000 多个真实浏览器的在线 Selenium Grid)对其执行 Selenium 测试。

CI/CD 基础

CI/CD 是一组最佳实践,可确保您以一致且可靠的方式向 Web 应用程序提供产品更新。您的 Web 应用程序必然会随着进入新发布周期的每个冲刺而增长。最初,您可能有一个小团队负责 Web 应用程序中的代码更改。在这种情况下,您不介意直接做所有事情,您构建代码,自己测试并将其部署到生产环境中。

但是,随着团队的成长,将会有很多交互点,并且当您尝试将所有代码更改从一个暂存环境迁移到另一个时,出错的可能性也会增加。这就是 CI/CD 管道发挥关键作用的地方。

任何在线运行的成功业务都高度依赖于其 CI/CD 管道的配置方式。在解释 Uber](http://highscalability.com/blog/2016/10/12/lessons-learned-from-scaling-uber-to-2000-engineers-1000-ser.html)的增长和可扩展性的[文章中的一篇文章中说:

优步现在遍布 400 个城市和 70 个国家。他们有超过 6000 名员工,其中 2000 名是工程师。仅仅一年半以前,只有 200 名工程师。这些工程师已经生产了 1000 多个微服务,这些微服务存储在 8000 多个 git 存储库中。

如果您观察一家企业的发展速度有多快,那么您可以想象,如果 Uber 没有整合 CI/CD 管道,那么在短短一年半的时间里,与 10 倍工程师进行协调可能会遇到的挑战。在当今世界,如果不遵循 CI/CD 最佳实践,很难想象一个 Web 应用程序在速度和一致性方面可扩展。现在,什么是 CI 和 CD? CI指的是持续集成,CD指的是持续交付。两者结合可以实现持续部署。让我们看看它们的真正含义。!

什么是持续集成?

在传统的 SDLC 模型中,开发人员会将新功能一个一个孤立地迁移到一个环境中。当您有多个开发人员处理多个功能时,这会产生问题。持续集成是一种实践,可确保开发人员能够以系统的方式通过共享存储库向 Web 应用程序的主分支提交大量更改。通过利用持续集成实践,您的开发人员可以每天多次将围绕修补程序、产品增强等的代码集成到共享存储库中。这样,您的整体上市速度可以加快,让您变得敏捷。

如果您已将 GitHub 存储库的编辑权限授予团队中的开发人员,那么您只需确保开发人员遵循最佳实践、代码样式,最重要的是测试用例没有失败。只要满足这些要求,您就不应禁止任何人签入您的代码。这将有助于您的公司不断扩大规模。

什么是持续交付?

谈到持续交付,它只发生在 CI 执行之后。顾名思义,持续交付实践可确保您配置了一个自动管道,以将代码更改从一个登台环境部署到另一个。

持续交付包括使您的软件可部署所需的所有步骤。这包括运行综合测试、使用测试工具的质量保证、构建执行、代码签名、文档和部署到预生产或用户接受环境。

不要将持续交付与持续部署混淆!!

将持续交付视为除部署之外的一切。您准备部署,但实际上并未将其部署到生产服务器。您将其留给人工干预步骤,这些步骤实际上将确保何时何地进行部署。持续交付适用于不需要持续部署的团队。但是,持续部署是一种实践,只有在您设置了定义明确的迁移系统时才能实施,这对于员工较少的组织来说是不可行的。这给我们带来了下一个问题。!

什么是持续部署?

持续部署实际上跟随着持续交付。它是持续交付的延伸。它使持续交付更进了一步,在生产环境中自动执行新版本的部署。

持续部署的唯一要求是流程、检查和测试设置,保证无崩溃体验。现在,由于它是一个完全自动化的系统,因此你们必须花更多时间开发非常严格的测试用例,因为在这里您没有任何机会手动审查您的迁移,一旦它消失了,它就消失了。!

这就是为什么持续部署对所有公司都不可行的原因。持续部署在部署代码之前应该有最严格的规则,因为这个过程是完全自动化的系统,没有任何人工干预。

作为自动化流水线生产链的最后一步,这一级别的检查和测试必须是最严格的,任何低于 100% 的东西都应该被拒绝,没有任何回旋余地。

尽管持续部署带来了所有好处,但团队应该验证需求,并且只有在开发环境、生产敏感性和测试系统允许无缝采用时才采用持续部署。

请记住,如果该地方的系统不够成熟,那么部署对于任何团队来说都可能是灾难性的。这就是为什么大多数团队只使用持续交付并且这样做没有害处。这完全取决于您要构建什么以及它的重要性,并且没有硬性规定您应该实际使用持续部署。

什么是 GitLab CI/CD?

Gitlab 为托管在 Gitlab 以及其他 git 提供商上的项目提供了出色的 CI/CD 产品。使用 GitLab CI/CD,您可以合并我们讨论的所有三个阶段,即持续集成、持续交付和持续部署。

GitLab CI/CD 的强大之处在于它允许您将 Git 存储库托管到任何其他 Git 提供商(例如 GitHub),并且您仍然可以利用它的 CI/CD 系统。您甚至无需更改您的 Git 提供程序即可使用 get Gitlab CI/CD。运行 CI/CD 的唯一要求是存在特殊的 GitLab CI YAML 配置文件。 GitLab CI YAML 文件包含运行不同 CI/CD 管道所需的所有指令和数据。

有许多定制选项可用于根据定制需求塑造管道。

另一个需要注意的关键是 .gitlab-ci.yml 是受版本控制的并放置在存储库中。这甚至允许您的存储库的旧版本成功构建,使您的团队更容易采用 CI 实践。原因是,如果 GitLab CI YAML 放置在存储库本身中,则意味着您现在已将 CI/CD 的逻辑放入存储库中。让您无需担心 CI/CD 系统可能会出现故障和丢失数据。现在,无论代码在哪里,您的 CI/CD 都在那里,只要假设相同的管道,就可以更轻松地从一个托管环境转移到另一个托管环境。这样,您的团队可以轻松地将 CI 分支用作特殊的不同管道和作业,您对所有 CI CD 管道都有一个单一的事实来源。

什么是 GitLab CI/CD 环境变量?

环境变量是动态命名的值,可用于使 CI/CD 管道完全动态和参数化。一般来说,最好的做法是不断删除硬编码值并使用环境变量来使作业可移植且与提供者无关。

具体来说,Gitlab 有大量预定义变量,可以帮助构建强大而灵活的 CI/CD 管道。该链接可用于查看完整列表。

最常用和最重要的变量包括:

  • CI_COMMIT_REF_NAME

  • CI_COMMIT_BRANCH

  • CI\提交\标签

  • CI_EXTERNAL_PULL_REQUEST_IID

  • 包括其他

这些变量允许根据不同的 git 分支来塑造管道,IMO 这为根据环境区分作业提供了很大的灵活性。要了解更多信息,您可以查看环境变量](https://docs.gitlab.com/ee/ci/variables/)的[GitLab 官方文档。

使用尽可能多的环境变量来使您的工作可定制和灵活总是更好。

什么是 GitLab 缓存依赖项?

每个 CI/CD 作业都需要某种构建阶段,其中 est 目标是使用 3rd 方依赖项构建的。根据堆栈,这些依赖项是使用插件管理器、模块导入器等获取的。在所有语言中使用 3rd 方模块构建的共同痛点是从 3rd 方源获取依赖项并编译它们需要大量时间.想象一下,每天为多个项目执行此过程超过一百次,并计算它所导致的时间和资源浪费。不是一张令人愉快的照片,对吧?

如果有办法缓存这些构建的依赖项并将这些缓存的依赖项用于多个管道,它将使 CI 构建速度更快,减少带宽浪费,并疏通 CI 管道,以便相同的 Infra 可用于更多构建。GitLab 的缓存依赖项允许您直接从 .gitlab-ci.yaml 文件中直接执行此操作。

就像在 yaml 文件和 key 属性中设置缓存字典一样简单。只需确保在需要缓存目录的所有作业中使用相同的键即可。确保分支之间缓存的常见做法是使用 git 基础环境变量作为缓存键。例如,CI_COMMIT_BRANCH 可以帮助您在为分支运行作业时利用缓存。

Gitlab CI/CD 提供了强大的原语来使缓存失效。这可以通过 UI 或清除缓存键来完成。

扩展:您可以选择获取依赖项并仅构建它们包清单文件更改。这优于总是使用缓存。例如,仅在 package.json 更改时获取节点 js 依赖项。

如何触发 CI/CD 管道?

GitLab CI/CD 允许您使用以下方式触发管道:

  1. 基于 Git 的触发器

  2. Webhook/ Cron

  3. 人工干预

基于 Git 的触发器 - 触发 CI/CD 的最简单方法是执行任何基于 git 的操作,例如推送分支、合并拉取请求或创建在 gitlab.yaml 文件中提到处理程序的标签。这是触发 CI/CD 最常用和最方便的方法。

Webhooks 通过对专用 URL 进行 HTTP 发布调用,提供了一种按需触发 CI /CD 的便捷方法。这对于基于事件的触发非常有用,只要发生所需的事件,就可以调用 webhook

  • 例如,您可以设置一个 cron 以在 Gitlab 上运行夜间构建,只需在所需的时间间隔点击 webhook url 上的 curl 请求。

  • 可以使用任何其他事件,只要 Webhook 可以响应该事件。

Gitlab 有一项规定,可以请求授权用户的手动干预以继续工作的后续步骤。在 gitlab.yaml 中,您可以提及只有在团队中具有访问权限的人可以从 UI 恢复工作之后才能运行的管道的一部分。

  • 此功能可以构建我们已经讨论过的持续交付管道。除了部署之外的一切都可以自动化,只有在人工干预之后,才能进行部署。

GitLab CI/CD 的专有参数:仅和除外

Only 和 except 是设置作业策略以限制何时创建作业的两个参数。这些构造是 gitlab CI/CD 管道的基本要素,它允许自定义和有条件地执行作业,以根据您自己的需要来塑造它。

1.“仅”指定作业将触发的分支和标签的名称。

  1. ‘Except’指定作业不会触发的分支和标签的名称。

Only 和 except 本质上是包容性的,并允许使用正则表达式。这是一个非常有趣的功能,可以通过弹奏弦乐进行任何类型的自定义。

Only 和 except 允许指定存储库路径来过滤分叉作业。 only 和 except 的一些有趣的值是:

  • 个分支

  • 个标签

  • 合并_requests

这是基于 Gitlab CI/CD 支持基于 git 的应用程序的前提条件。如果您在 only 或 except 下使用多个键,则这些键将被评估为单个联合表达式。那是:

仅限* :表示“如果所有条件都匹配,则包含此作业”。

  • except:表示“如果任何条件匹配,则排除此作业”。

使用 only,单个键由 AND 逻辑连接:

except 被实现为这个完整表达式的否定:

这意味着键被视为由 OR 连接。这种关系可以描述为:

有大量的属性可以被 only 和 except 条件使用。我真的建议您检查这些值。

为 GitLab CI/CD 执行 Selenium 测试脚本

让我们应用我们在这里学到的东西。!我们今天将在本 GitLab CI/CD 教程中使用的项目是HourGlass 2018,它是一个 MERN(Mongo Express React 和 Nodejs)堆栈应用程序。这是一个使用当时可用的最佳实践的简单时间管理应用程序。不幸的是,在 JS 世界中,这些最佳实践每个月都会发生变化,其中一些可能已经更新,但大多数仍然相关,这是一个完整的生产规模和生产风格的开发存储库,包含所有可用的最佳实践。

测试 Web 应用程序 URL:hourglass.surge.sh

克隆 GitHub 存储库

确保将HourGlass GitHub 存储库克隆到 GitLab CI 实例。克隆后,路由到主分支并检查GitLab YAML 文件。


image: node:10.19.0

stages:
  - install_dependencies
  - build
  - test
  - deploy

install_dependencies:
  stage: install_dependencies
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
  script:
    - yarn install
  only:
    changes:
      - yarn.lock

#continuous integration
unit-test:
  stage: test
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  script:
    yarn test

# Only runs in case of continuous delivery
integration-test:
  stage: test
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  services:
  - mongo
  script:
    - echo $MONGO_URI_TESTS
    - yarn test:integration
  only:
  - merge_requests
  - prod



lint:
  stage: test
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  script: yarn lint


e2e-test:
  stage: test
  services:
  - mongo
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - build/
    policy: pull
  script:
    - node node_modules/node-static/bin/cli.js build --port 5000 --spa &
    - yarn start-prod-server
    - node node_modules/pm2/bin/pm2 logs &
    - sleep 3
    - yarn run test:e2e
  dependencies:
    - Build-client

  only:
    refs:
      - tags
  except:
    - /^((?!release).)*$/


Build-client:
  stage: build
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
      - build/
    policy: pull-push

  script: yarn build-client
  artifacts:
    paths:
    - build




Build-docs:
  stage: build
  script: yarn docs
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  only:
  - merge_requests
  - prod

Build-storybook:
  stage: build
  script: yarn build-storybook
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  only:
  - merge_requests
  - prod



deploy-backend:
  stage: deploy
  image: ruby:latest
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  script:
    - apt-get update -qy
    - apt-get install -y ruby-dev
    - gem install dpl
    - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API_KEY
  dependencies:
    - e2e-test
  when: manual
  allow_failure: false
  only:
    refs:
      - tags
  except:
    - /^((?!release).)*$/



deploy-frontend:
  stage: deploy
  cache:
    key: $CI_COMMIT_REF_SLUG-$CI_PROJECT_DIR
    paths:
      - node_modules/
    policy: pull
  variables:
    REACT_APP_API_HOST: $REACT_APP_API_HOST_PROD
  script:
    - yarn build-client
    - node ./node_modules/.bin/surge -p build/ --domain $SURGE_DOMAIN
  when: manual
  allow_failure: false
  dependencies:
    - e2e-test
  only:
    refs:
      - tags
  except:
    - /^((?!release).)*$/

进入全屏模式 退出全屏模式

在 GitLab CI 中配置 CI/CD 管道

为了触发我们的 CI/CD 管道,我们需要编辑 README.md 文件。我们可以直接通过 Web IDE 进行这些更改。我们可以继续添加示例评论并点击提交。确保在主分支中执行更改。

提交代码后,您可以通过 GitLab 跳转到 CI/CD 部分,并注意作业已成功执行。您可以在运行状态中找到以下内容:

  • 构建客户端

  • 起绒

  • 单元测试

[gitlab-file](https://res.cloudinary.com/practicaldev/image/fetch/s--demdgc_D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/gitlab-file.png)

现在,向生产分支提出拉/合并请求。对于演示,我们将生产分支保留为 Git 存储库的主分支,而不是主分支。现在,我们需要从 master 合并到 prod。

[gitlab-CICD](https://res.cloudinary.com/practicaldev/image/fetch/s--HKrSLGUF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/gitlab-CICD.png)

注意: 默认情况下,在您提交合并请求之前,您会发现勾选了“当合并请求被接受时删除源分支”复选框。取消选中该复选框。

除非管道成功,否则 GitLab CI/CD 不会执行合并。这意味着在您的测试脚本完成自己执行之前,它不会通过更改。这是一个很棒的功能,可以帮助您通过稳定版本。此外,您不必等待测试脚本完成即可执行合并。您需要做的就是单击“管道成功时合并”按钮,GitLab CI 将负责合并后您的测试脚本执行。

[sample-pull-request](https://res.cloudinary.com/practicaldev/image/fetch/s--7NaQTLer--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn .lambdatest.com/blog/wp-content/uploads/2020/03/sample-pull-request.png)

默认情况下,所有作业都将并行执行,除非您另有指定。这大大减少了 CI 过程消耗的总时间。在您点击合并请求后,您可以看到正在传递哪些构建以及正在运行哪些作业。

[gitlab-CICD-管道](https://res.cloudinary.com/practicaldev/image/fetch/s--dkgRvb4Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www .lambdatest.com/blog/wp-content/uploads/2020/03/gitlab-CICD-pipeline.png)

现在,您可能会注意到集成测试将在分离的管道中运行,以及之前讨论的最新管道中的作业,即 Linting 和单元测试。

[gitlab-repo](https://res.cloudinary.com/practicaldev/image/fetch/s--WhEbmXMD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/gitlab-repo.png)

集成测试将确保您的后端和前端保持良好同步,并且您的 API 响应良好。

为发布创建标签

要创建一个版本,我们首先需要通过 GitLab CI/CD 生成一个标签。

[! CI-CD] (https://res.cloudinary.com/practicaldev/image/fetch/s--Z7leiXUP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/CI-CD.png)

如果您想为一些重要的更改添加书签,Git 标签非常有用。现在,从 master 分支创建一个新版本。您可以在此处添加任何消息以帮助您记住此主要版本包含的内容,或者您也可以添加一些构建版本检查点。然后您可以创建标签:

[git-tag](https://res.cloudinary.com/practicaldev/image/fetch/s--mah_zc6M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/git-tag.png)

一旦创建了标签,就会开始执行一个进程。现在,您可以直接查看 CICD 并注意顶部创建的新管道,以响应标签创建事件。

在此管道中,您可以注意到有 4 个阶段。第一个是确保安装了所有依赖项。这一点至关重要,因为您现在正在创建最终构建,并且您不希望在其中忽略任何混淆、错误、警告。

[gitlab-pipeline](https://res.cloudinary.com/practicaldev/image/fetch/s--VU03mDRW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/gitlab-pipeline.png)

下一阶段是建立构建客户端的地方。

[gitlab-CICD](https://res.cloudinary.com/practicaldev/image/fetch/s--ax4gpmUH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/gitlab-CICD-1.png)

第三,您会注意到将运行的三种测试,即单元测试用例、linting,然后是端到端测试。端到端测试是您将合并 Selenium 测试脚本以执行跨浏览器测试的地方。然后,您将通过 LambdaTest 提供的在线 Selenium Grid 执行这些测试脚本。

最后,当 Selenium 测试脚本通过时,管道将进入下一阶段,将在后端和前端阶段进行部署。

[硒测试脚本](https://res.cloudinary.com/practicaldev/image/fetch/s--coeOVRRy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn .lambdatest.com/blog/wp-content/uploads/2020/03/selenium-testing-script.png)

注意: 这些阶段是手动触发的。一旦管道进入该阶段,您将找到一个播放按钮。

[git tag](https://res.cloudinary.com/practicaldev/image/fetch/s--cNtT1-sY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn . lambdatest.com/blog/wp-content/uploads/2020/03/git-tag-1.png)

这就对了..!现在,一旦你按下播放按钮。您可以将更改部署到相应的环境。您可以继续通过LambdaTest 自动化仪表板验证您的 Selenium 测试脚本。

[lambdatest-dashboard](https://res.cloudinary.com/practicaldev/image/fetch/s--yGL3ih09--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/lambdatest-dashboard.png)

因此,您可以在 Selenium 测试的录制视频中看到。这与我们一直在尝试部署的 HourGlass 应用程序相同。请注意,可以使用 127.0.0.1:5000 通过 localhost 访问 Web 应用程序。这是您运行静态服务器来托管前端文件和单独的后端服务器的步骤。稍后,您可以使用 Lambda 隧道运行端到端测试。

[lambda-隧道](https://res.cloudinary.com/practicaldev/image/fetch/s--vqreIJT1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/lambda-tunnel.png)

使用 LambdaTest Selenium Grid 进行连续测试

正如您所注意到的,我们在 LambdaTest 的在线 Selenium Grid 上运行我们的脚本。需要什么?好吧,这样做可以帮助您快速自动地将代码更改验证到通过 CI/CD 管道迁移到的暂存环境中。这样,您就可以不断地集成新功能的代码,不断地将它们从一个暂存环境部署到另一个环境,现在,您也可以持续测试这些代码更改。因此,每次将代码提交到您已准备好 Selenium 测试脚本的分支时,该代码将被验证以进行浏览器兼容性测试。让您在持续测试的帮助下加快测试周期。

现在,让我们思考一下我们如何通过 LambdaTest 在本地托管的 Web 应用程序上运行 Selenium 测试。这是用于 Jest 框架](https://gitlab.com/psych0der/hourglass/-/blob/master/src/e2e/lt.test.js)执行自动化浏览器测试的[Selenium 测试脚本。

const webdriver = require('selenium-webdriver');
const { until } = require('selenium-webdriver');
const { By } = require('selenium-webdriver');
const lambdaTunnel = require('@lambdatest/node-tunnel');

const username = process.env.LT_USERNAME || 'Your_LambdaTest_Username';
const accessKey =
  process.env.LT_ACCESS_KEY ||
  'Your_LambdaTest_Access_Key';

const capabilities = {
  build: 'jest-LambdaTest-Single',
  browserName: 'chrome',
  version: '72.0',
  platform: 'WIN10',
  video: true,
  network: true,
  console: true,
  visual: true,
  tunnel: true,
};

const tunnelInstance = new lambdaTunnel();

const tunnelArguments = {
  user: process.env.LT_USERNAME || Your_LambdaTest_Username',
  key:
    process.env.LT_ACCESS_KEY ||
    'Your_LambdaTest_Access_Key',
};

const getElementById = async (driver, id, timeout = 2000) => {
  const el = await driver.wait(until.elementLocated(By.id(id)), timeout);
  return await driver.wait(until.elementIsVisible(el), timeout);
};

const getElementByClassName = async (driver, className, timeout = 2000) => {
  const el = await driver.wait(
    until.elementLocated(By.className(className)),
    timeout
  );
  return await driver.wait(until.elementIsVisible(el), timeout);
};

const getElementByName = async (driver, name, timeout = 2000) => {
  const el = await driver.wait(until.elementLocated(By.name(name)), timeout);
  return await driver.wait(until.elementIsVisible(el), timeout);
};

const getElementByXpath = async (driver, xpath, timeout = 2000) => {
  const el = await driver.wait(until.elementLocated(By.xpath(xpath)), timeout);
  return await driver.wait(until.elementIsVisible(el), timeout);
};

function timeout(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

describe('webdriver', () => {
  let driver;

  beforeAll(async () => {
    const istunnelStarted = await tunnelInstance.start(tunnelArguments);
    driver = new webdriver.Builder()
      .usingServer(
        'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
      )
      .withCapabilities(capabilities)
      .build();
    // eslint-disable-next-line no-undef
    await driver.get(`http://127.0.0.1:5000/signup`); // https://hourglass.surge.sh/signup
  }, 20000);

  afterAll(async () => {
    await driver.quit();
    await tunnelInstance.stop();
  }, 15000);

  test(
    'Signup test',
    async () => {
      const nameInput = await getElementById(driver, 'name');
      await nameInput.clear();
      await nameInput.sendKeys('Mayank');

      const emailInput = await getElementById(driver, 'email');
      await emailInput.clear();
      await emailInput.sendKeys('mybach8@gmail.com');

      const passwordInput = await getElementById(driver, 'password');
      await passwordInput.clear();
      await passwordInput.sendKeys('password');

      const cnfPassInput = await getElementById(driver, 'confirmPassword');
      await cnfPassInput.clear();
      await cnfPassInput.sendKeys('password');

      const prefWorkingHours = await getElementById(
        driver,
        'preferredWorkingHours'
      );
      await prefWorkingHours.clear();
      await prefWorkingHours.sendKeys('10.0');

      const btn = await getElementByClassName(
        driver,
        'LoaderButton  btn btn-lg btn-default btn-block'
      );
      await btn.click();

      await timeout(2000);
      const successText = await getElementByClassName(
        driver,
        'registerSuccess'
      );
      const successTextValue = await successText.getText();
      console.log(successTextValue);
      return expect(successTextValue).toContain('Congratulations');
    },
    20000
  );

  test(
    'Login test',
    async () => {
      await driver.get(`http://127.0.0.1:5000/login`); // https://hourglass.surge.sh/signup
      // const lnk = await getElementByName(driver, 'li1');
      // await lnk.click();

      // const lnk1 = await getElementByName(driver, 'li2');
      // await lnk1.click();

      const emailInput = await getElementById(driver, 'email');
      await emailInput.clear();
      await emailInput.sendKeys('mybach8@gmail.com');

      const passwordInput = await getElementById(driver, 'password');
      await passwordInput.clear();
      await passwordInput.sendKeys('password');

      const btn = await getElementByClassName(
        driver,
        'btn btn-lg btn-default btn-block'
      );
      await btn.click();

      await timeout(2000);
      const successText = await getElementByClassName(
        driver,
        'btn btn-primary'
      );
      const successTextValue = await successText.getText();
      console.log(successTextValue);
      expect(successTextValue).toContain('Manage Time tracks');
    },
    20000
  );
});

进入全屏模式 退出全屏模式

代码演练

我们在这里使用了一些隧道参数来构建 Lambda 隧道。 LambdaTest 发布了一个npm 包来自动设置隧道。

const lambdaTunnel = require('@lambdatest/node-tunnel');

进入全屏模式 退出全屏模式

我们在这里所做的是在每次测试之前设置一个新的 WebDriver,该驱动程序针对 LambdaTest Selenium Grid Hub 的公共 URL。我们正在使用 LambdaTest 账户提供的用户名和访问密钥。

const tunnelArguments = {
  user: process.env.LT_USERNAME || Your_LambdaTest_Username',
  key:
    process.env.LT_ACCESS_KEY ||
    'Your_LambdaTest_Access_Key',
};

进入全屏模式 退出全屏模式

然后,您提供所有功能,例如您希望通过特定浏览器、浏览器版本、操作系统、视频录制等使用 Lambda 隧道。

.usingServer(
        'https://' + username + ':' + accessKey + '@hub.lambdatest.com/wd/hub'
      )

进入全屏模式 退出全屏模式

在线 Selenium Grid 的远程 WebDriver 设置完成后,我们正在等待加载注册页面。

await driver.get(`http://127.0.0.1:5000/signup`); // https://hourglass.surge.sh/signup
  }, 20000);

进入全屏模式 退出全屏模式

下面的代码将确保隧道实例首先启动,然后才会执行您的 Selenium 测试脚本。

const istunnelStarted = await tunnelInstance.start(tunnelArguments);

进入全屏模式 退出全屏模式

最佳实践: 完成所有测试用例后,请确保删除隧道实例。这将有助于保存您在 LambdaTest Selenium 网格上可用的并发限制。每个计数可以运行的隧道数量有限,具体取决于您选择的 LambdaTest 定价以及是否让隧道处于运行状态,即使在执行测试之后也是如此。它不允许您在其他测试用例中使用隧道,因此最好在测试完成后关闭隧道。

afterAll(async () => {
    await driver.quit();
    await tunnelInstance.stop();
  }, 15000);

进入全屏模式 退出全屏模式

监控日志

LambdaTest 为您提供了一个直观的界面来分析 Selenium 测试脚本的结果。您可以获得各种日志,例如网络日志、命令日志、原始 Selenium 日志、元数据。您还可以录制整个脚本执行的视频,以及逐个命令的屏幕截图。您可能会注意到测试已通过 LambdaTest 的在线 Selenium Grid 成功触发。

[监控日志](https://res.cloudinary.com/practicaldev/image/fetch/s--MVI-vb97--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn .lambdatest.com/blog/wp-content/uploads/2020/03/monitor-logs.png)

您可以访问自动化仪表板中不同类型的选项卡,以找出调试脚本时出了什么问题。以下是提供日志的方式。

硒原木

[自动化日志](https://res.cloudinary.com/practicaldev/image/fetch/s--lXRtrlOg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest .com/blog/wp-content/uploads/2020/03/automation-logs.png)

命令日志

[自动化日志](https://res.cloudinary.com/practicaldev/image/fetch/s--Q9ZCim1D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.lambdatest.com /blog/wp-content/uploads/2020/03/automationlogs.png)

控制台日志

[lambdatest-automation-logs](https://res.cloudinary.com/practicaldev/image/fetch/s--twVd3DGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn .lambdatest.com/blog/wp-content/uploads/2020/03/lambdatest-automation-logs.png)

最终,GitLab CI 将在 Heroku 上部署后端,在 Surge 上部署前端。因此,打开 URL 后,您可以看到前端部署在 Serge 上,而我的后端部署在 Heroku 上,这是由 GitLab CI/CD 管道自动完成的。

现在,在我们结束这个 GitLab CI/CD 教程之前,让我们快速记下一些您需要牢记的 CI/CD 最佳实践。

CI/CD 最佳实践

现在,您已经掌握了有关利用 GitLab CI 管道进行 Selenium 测试的大量知识。我建议您记下这些 CI/CD 最佳实践,以更快地构建更好的 Web 应用程序。

快速构建和快速测试

CI/CD 系统的成功取决于执行速度。如果 CI 周期每次提交都花费大量时间,开发人员将找到替代且更快的绕过方法来快速集成他们的代码。这通常涉及跳过测试以支持乐观更新的路径。这可能会对生产造成严重破坏。我想我什至不需要提及集成未经测试的代码的后果。

CI/CD 环境应该得到保护

经常被忽略,但保护您的 CI/CD 环境非常重要。它是需要保护的最敏感的基础设施之一,因为它包含对您的代码库、高度敏感数据和各种环境的访问。此外,它是大型高频开发团队中最常用的系统之一。在最坏的情况下,CI/CD 上的任何中断都可能导致巨大的生产力损失和财务损失。

CI/CD 应该是部署到生产的唯一途径

CI/CD 管道与最后一个使用它们的人一样成功。如果团队不采用,开发 CI/CD 的所有努力都会失败。 CI/CD 应该是部署到产品的唯一方式。事实上,回滚应该通过相同的管道部署。

始终在 CI/CD 管道中保留回滚选项

回滚更改的能力不应涉及复杂的程序。回滚更改应该尽可能简单。最好在凌晨 3 点回滚更改,而不是在生产环境中调试它们。

提前失败

您应该在管道的早期运行最快的测试。这个想法是如果构建未通过任何测试,则拒绝该构建。尽早拒绝可以节省大量时间,使周转时间非常短。

在提交到 CI/CD 管道之前在本地运行测试

CI 在您的本地开发系统上启动。所有基本 CI 测试都应首先在您的本地系统上运行,因为它速度快、节省时间并节省平台上的 CI/CD 基础设施,以用于更关键的和后期阶段的管道。

测试应该在临时环境中运行

为了为 CI/CD 管道提供一致的结果,每次测试都以全新的状态运行非常重要。临时环境是使测试幂等的必要条件。容器是一个合适的环境,因为它们可以很容易地提供一个新鲜的环境。

解耦部署与发布

正如在持续交付介绍中提到的,将部署与发布过程解耦使得发布过程成为纯粹的营销和战略团队决策。这在灵活性和速度方面具有巨大的优势。

结束

赞!!您现在已成功执行 Selenium 测试,以在发布版本部署之前执行各种检查。 GitLab CI 为您准备了发布过程,并通过允许您与 2000 多个真实浏览器的在线 Selenium Grid 集成,从而消除了进行最后一分钟检查的所有麻烦,确保您的 Web 应用程序的无缝 UI。

这就是我们如何为您的公司或团队实际构建强大的 CI/CD 管道的方式。如果您仍然感到困惑,可以查看我们的网络研讨会“使用 GitLab CI 和 LambdaTest确保稳健的 CI/CD 管道”。不用担心我们在网络研讨会中也使用了相同的代码!!

如果您仍有任何疑问,请随时在下面的评论部分发表。祝测试愉快! 🙂

cross_browser_testing_tool

Logo

CI/CD社区为您提供最前沿的新闻资讯和知识内容

更多推荐