commit 9185128d87abe5b04447f522989cd93039c1cf84 Author: debugtalk Date: Fri Nov 1 23:17:45 2019 +0800 Deployed e5fdf96 with MkDocs version: 1.0.4 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..f909cd54 --- /dev/null +++ b/404.html @@ -0,0 +1,693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + +
+
+ +

404 - Not found

+ + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..3c1a8252 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +docs.httprunner.org diff --git a/FAQ/index.html b/FAQ/index.html new file mode 100644 index 00000000..978a9714 --- /dev/null +++ b/FAQ/index.html @@ -0,0 +1,761 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FAQ - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

常见问题

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Installation/index.html b/Installation/index.html new file mode 100644 index 00000000..18ad4555 --- /dev/null +++ b/Installation/index.html @@ -0,0 +1,971 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 安装说明 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

安装说明

+ +

运行环境

+

HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS、Linux、Windows 系统平台上。

+

Python 版本:HttpRunner 支持 Python 3.4 及以上的所有版本,并使用 Travis-CI 进行了持续集成测试,测试覆盖的版本包括 2.7/3.4/3.5/3.6/3.7。虽然 HttpRunner 暂时保留了对 Python 2.7 的兼容支持,但强烈建议使用 Python 3.4 及以上版本。

+

操作系统:推荐使用 macOS/Linux。

+

安装方式

+

HttpRunner 的稳定版本托管在 PyPI 上,可以使用 pip 进行安装。

+
$ pip install httprunner
+
+ + +

如果你需要使用最新的开发版本,那么可以采用项目的 GitHub 仓库地址进行安装:

+
$ pip install git+https://github.com/HttpRunner/HttpRunner.git@master
+
+ + +

版本升级

+

假如你之前已经安装过了 HttpRunner,现在需要升级到最新版本,那么你可以使用-U参数。该参数对以上三种安装方式均生效。

+
$ pip install -U HttpRunner
+$ pip install -U git+https://github.com/HttpRunner/HttpRunner.git@master
+
+ + +

安装校验

+

在 HttpRunner 安装成功后,系统中会新增如下 5 个命令:

+
    +
  • httprunner: 核心命令
  • +
  • ate: 曾经用过的命令(当时框架名称为 ApiTestEngine),功能与 httprunner 完全相同
  • +
  • hrun: httprunner 的缩写,功能与 httprunner 完全相同
  • +
  • locusts: 基于 Locust 实现性能测试
  • +
  • har2case: 辅助工具,可将标准通用的 HAR 格式(HTTP Archive)转换为YAML/JSON格式的测试用例
  • +
+

httprunner、hrun、ate 三个命令完全等价,功能特性完全相同,个人推荐使用hrun命令。

+

运行如下命令,若正常显示版本号,则说明 HttpRunner 安装成功。

+
$ hrun -V
+2.0.2
+
+$ har2case -V
+0.2.0
+
+ + +

开发者模式

+

默认情况下,安装 HttpRunner 的时候只会安装运行 HttpRunner 的必要依赖库。

+

如果你不仅仅是使用 HttpRunner,还需要对 HttpRunner 进行开发调试(debug),那么就需要进行如下操作。

+

HttpRunner 使用 pipenv 对依赖包进行管理,若你还没有安装 pipenv,需要先执行如下命令进行按照:

+
$ pip install pipenv
+
+ + +

获取 HttpRunner 源码:

+
$ git clone https://github.com/HttpRunner/HttpRunner.git
+
+ + +

进入仓库目录,安装所有依赖:

+
$ pipenv install --dev
+
+ + +

运行单元测试,若测试全部通过,则说明环境正常。

+
$ pipenv run python -m unittest discover
+
+ + +

查看 HttpRunner 的依赖情况:

+
$ pipenv graph
+
+HttpRunner==2.0.0
+  - colorama [required: Any, installed: 0.4.0]
+  - colorlog [required: Any, installed: 3.1.4]
+  - har2case [required: Any, installed: 0.2.0]
+    - PyYAML [required: Any, installed: 3.13]
+  - Jinja2 [required: Any, installed: 2.10]
+    - MarkupSafe [required: >=0.23, installed: 1.0]
+  - PyYAML [required: Any, installed: 3.13]
+  - requests [required: Any, installed: 2.20.0]
+    - certifi [required: >=2017.4.17, installed: 2018.10.15]
+    - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
+    - idna [required: >=2.5,<2.8, installed: 2.7]
+    - urllib3 [required: >=1.21.1,<1.25, installed: 1.24]
+  - requests-toolbelt [required: Any, installed: 0.8.0]
+    - requests [required: >=2.0.1,<3.0.0, installed: 2.20.0]
+      - certifi [required: >=2017.4.17, installed: 2018.10.15]
+      - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
+      - idna [required: >=2.5,<2.8, installed: 2.7]
+      - urllib3 [required: >=1.21.1,<1.25, installed: 1.24]
+
+ + +

调试运行方式:

+
# 调试运行 hrun
+$ pipenv run python main-debug.py hrun -h
+
+# 调试运行 locusts
+$ pipenv run python main-debug.py locusts -h
+
+ + +

Docker

+

TODO

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/font-awesome.css b/assets/fonts/font-awesome.css new file mode 100644 index 00000000..b476b53e --- /dev/null +++ b/assets/fonts/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url("specimen/FontAwesome.woff2") format("woff2"),url("specimen/FontAwesome.woff") format("woff"),url("specimen/FontAwesome.ttf") format("truetype")}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} \ No newline at end of file diff --git a/assets/fonts/material-icons.css b/assets/fonts/material-icons.css new file mode 100644 index 00000000..d23d365e --- /dev/null +++ b/assets/fonts/material-icons.css @@ -0,0 +1,13 @@ +/*! + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, SOFTWARE + * DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + * SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS AND + * LIMITATIONS UNDER THE LICENSE. + */@font-face{font-family:"Material Icons";font-style:normal;font-weight:400;src:local("Material Icons"),local("MaterialIcons-Regular"),url("specimen/MaterialIcons-Regular.woff2") format("woff2"),url("specimen/MaterialIcons-Regular.woff") format("woff"),url("specimen/MaterialIcons-Regular.ttf") format("truetype")} \ No newline at end of file diff --git a/assets/fonts/specimen/FontAwesome.ttf b/assets/fonts/specimen/FontAwesome.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/assets/fonts/specimen/FontAwesome.ttf differ diff --git a/assets/fonts/specimen/FontAwesome.woff b/assets/fonts/specimen/FontAwesome.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/assets/fonts/specimen/FontAwesome.woff differ diff --git a/assets/fonts/specimen/FontAwesome.woff2 b/assets/fonts/specimen/FontAwesome.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/assets/fonts/specimen/FontAwesome.woff2 differ diff --git a/assets/fonts/specimen/MaterialIcons-Regular.ttf b/assets/fonts/specimen/MaterialIcons-Regular.ttf new file mode 100644 index 00000000..7015564a Binary files /dev/null and b/assets/fonts/specimen/MaterialIcons-Regular.ttf differ diff --git a/assets/fonts/specimen/MaterialIcons-Regular.woff b/assets/fonts/specimen/MaterialIcons-Regular.woff new file mode 100644 index 00000000..b648a3ee Binary files /dev/null and b/assets/fonts/specimen/MaterialIcons-Regular.woff differ diff --git a/assets/fonts/specimen/MaterialIcons-Regular.woff2 b/assets/fonts/specimen/MaterialIcons-Regular.woff2 new file mode 100644 index 00000000..9fa21125 Binary files /dev/null and b/assets/fonts/specimen/MaterialIcons-Regular.woff2 differ diff --git a/assets/hogwarts.png b/assets/hogwarts.png new file mode 100644 index 00000000..5c111d64 Binary files /dev/null and b/assets/hogwarts.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 00000000..76d17f57 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/icons/bitbucket.1b09e088.svg b/assets/images/icons/bitbucket.1b09e088.svg new file mode 100644 index 00000000..cf58c14f --- /dev/null +++ b/assets/images/icons/bitbucket.1b09e088.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/icons/github.f0b8504a.svg b/assets/images/icons/github.f0b8504a.svg new file mode 100644 index 00000000..3d13b197 --- /dev/null +++ b/assets/images/icons/github.f0b8504a.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/icons/gitlab.6dd19c00.svg b/assets/images/icons/gitlab.6dd19c00.svg new file mode 100644 index 00000000..1d9fffa7 --- /dev/null +++ b/assets/images/icons/gitlab.6dd19c00.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/javascripts/application.ac79c3b0.js b/assets/javascripts/application.ac79c3b0.js new file mode 100644 index 00000000..a92563a3 --- /dev/null +++ b/assets/javascripts/application.ac79c3b0.js @@ -0,0 +1,60 @@ +!function(e,t){for(var n in t)e[n]=t[n]}(window,function(n){var r={};function i(e){if(r[e])return r[e].exports;var t=r[e]={i:e,l:!1,exports:{}};return n[e].call(t.exports,t,t.exports,i),t.l=!0,t.exports}return i.m=n,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=13)}([function(e,t,n){"use strict";var r={Listener:function(){function e(e,t,n){var r=this;this.els_=Array.prototype.slice.call("string"==typeof e?document.querySelectorAll(e):[].concat(e)),this.handler_="function"==typeof n?{update:n}:n,this.events_=[].concat(t),this.update_=function(e){return r.handler_.update(e)}}var t=e.prototype;return t.listen=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.addEventListener(e,n.update_,!1)})}),"function"==typeof this.handler_.setup&&this.handler_.setup()},t.unlisten=function(){var n=this;this.els_.forEach(function(t){n.events_.forEach(function(e){t.removeEventListener(e,n.update_)})}),"function"==typeof this.handler_.reset&&this.handler_.reset()},e}(),MatchMedia:function(e,t){this.handler_=function(e){e.matches?t.listen():t.unlisten()};var n=window.matchMedia(e);n.addListener(this.handler_),this.handler_(n)}},i={Shadow:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement&&n.parentNode instanceof HTMLElement))throw new ReferenceError;if(this.el_=n.parentNode,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLElement))throw new ReferenceError;this.header_=n,this.height_=0,this.active_=!1}var t=e.prototype;return t.setup=function(){for(var e=this.el_;e=e.previousElementSibling;){if(!(e instanceof HTMLElement))throw new ReferenceError;this.height_+=e.offsetHeight}this.update()},t.update=function(e){if(!e||"resize"!==e.type&&"orientationchange"!==e.type){var t=window.pageYOffset>=this.height_;t!==this.active_&&(this.header_.dataset.mdState=(this.active_=t)?"shadow":"")}else this.height_=0,this.setup()},t.reset=function(){this.header_.dataset.mdState="",this.height_=0,this.active_=!1},e}(),Title:function(){function e(e,t){var n="string"==typeof e?document.querySelector(e):e;if(!(n instanceof HTMLElement))throw new ReferenceError;if(this.el_=n,!((n="string"==typeof t?document.querySelector(t):t)instanceof HTMLHeadingElement))throw new ReferenceError;this.header_=n,this.active_=!1}var t=e.prototype;return t.setup=function(){var t=this;Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.update=function(e){var t=this,n=window.pageYOffset>=this.header_.offsetTop;n!==this.active_&&(this.el_.dataset.mdState=(this.active_=n)?"active":""),"resize"!==e.type&&"orientationchange"!==e.type||Array.prototype.forEach.call(this.el_.children,function(e){e.style.width=t.el_.offsetWidth-20+"px"})},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.width="",this.active_=!1},e}()},o={Blur:function(){function e(e){this.els_="string"==typeof e?document.querySelectorAll(e):e,this.index_=0,this.offset_=window.pageYOffset,this.dir_=!1,this.anchors_=[].reduce.call(this.els_,function(e,t){var n=decodeURIComponent(t.hash);return e.concat(document.getElementById(n.substring(1))||[])},[])}var t=e.prototype;return t.setup=function(){this.update()},t.update=function(){var e=window.pageYOffset,t=this.offset_-e<0;if(this.dir_!==t&&(this.index_=this.index_=t?0:this.els_.length-1),0!==this.anchors_.length){if(this.offset_<=e)for(var n=this.index_+1;ne)){this.index_=r;break}0=this.offset_?"lock"!==this.el_.dataset.mdState&&(this.el_.dataset.mdState="lock"):"lock"===this.el_.dataset.mdState&&(this.el_.dataset.mdState="")},t.reset=function(){this.el_.dataset.mdState="",this.el_.style.height="",this.height_=0},e}()},c=n(6),l=n.n(c);var u={Adapter:{GitHub:function(o){var e,t;function n(e){var t;t=o.call(this,e)||this;var n=/^.+github\.com\/([^/]+)\/?([^/]+)?.*$/.exec(t.base_);if(n&&3===n.length){var r=n[1],i=n[2];t.base_="https://api.github.com/users/"+r+"/repos",t.name_=i}return t}return t=o,(e=n).prototype=Object.create(t.prototype),(e.prototype.constructor=e).__proto__=t,n.prototype.fetch_=function(){var i=this;return function n(r){return void 0===r&&(r=0),fetch(i.base_+"?per_page=30&page="+r).then(function(e){return e.json()}).then(function(e){if(!(e instanceof Array))throw new TypeError;if(i.name_){var t=e.find(function(e){return e.name===i.name_});return t||30!==e.length?t?[i.format_(t.stargazers_count)+" Stars",i.format_(t.forks_count)+" Forks"]:[]:n(r+1)}return[e.length+" Repositories"]})}()},n}(function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLAnchorElement))throw new ReferenceError;this.el_=t,this.base_=this.el_.href,this.salt_=this.hash_(this.base_)}var t=e.prototype;return t.fetch=function(){var n=this;return new Promise(function(t){var e=l.a.getJSON(n.salt_+".cache-source");void 0!==e?t(e):n.fetch_().then(function(e){l.a.set(n.salt_+".cache-source",e,{expires:1/96}),t(e)})})},t.fetch_=function(){throw new Error("fetch_(): Not implemented")},t.format_=function(e){return 1e4=this.el_.children[0].offsetTop+(5-this.height_);e!==this.active_&&(this.el_.dataset.mdState=(this.active_=e)?"hidden":"")},t.reset=function(){this.el_.dataset.mdState="",this.active_=!1},e}()};t.a={Event:r,Header:i,Nav:o,Search:a,Sidebar:s,Source:u,Tabs:f}},function(t,e,n){(function(e){t.exports=e.lunr=n(24)}).call(this,n(4))},function(e,d,h){"use strict";(function(t){var e=h(8),n=setTimeout;function c(e){return Boolean(e&&void 0!==e.length)}function r(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],f(e,this)}function i(n,r){for(;3===n._state;)n=n._value;0!==n._state?(n._handled=!0,o._immediateFn(function(){var e=1===n._state?r.onFulfilled:r.onRejected;if(null!==e){var t;try{t=e(n._value)}catch(e){return void s(r.promise,e)}a(r.promise,t)}else(1===n._state?a:s)(r.promise,n._value)})):n._deferreds.push(r)}function a(t,e){try{if(e===t)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==typeof e||"function"==typeof e)){var n=e.then;if(e instanceof o)return t._state=3,t._value=e,void l(t);if("function"==typeof n)return void f((r=n,i=e,function(){r.apply(i,arguments)}),t)}t._state=1,t._value=e,l(t)}catch(e){s(t,e)}var r,i}function s(e,t){e._state=2,e._value=t,l(e)}function l(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var t=0,n=e._deferreds.length;t"+n+""};this.stack_=[],r.forEach(function(e,t){var n,r=a.docs_.get(t),i=f.createElement("li",{class:"md-search-result__item"},f.createElement("a",{href:r.location,title:r.title,class:"md-search-result__link",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article md-search-result__article--document"},f.createElement("h1",{class:"md-search-result__title"},{__html:r.title.replace(s,c)}),r.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:r.text.replace(s,c)}):{}))),o=e.map(function(t){return function(){var e=a.docs_.get(t.ref);i.appendChild(f.createElement("a",{href:e.location,title:e.title,class:"md-search-result__link","data-md-rel":"anchor",tabindex:"-1"},f.createElement("article",{class:"md-search-result__article"},f.createElement("h1",{class:"md-search-result__title"},{__html:e.title.replace(s,c)}),e.text.length?f.createElement("p",{class:"md-search-result__teaser"},{__html:function(e,t){var n=t;if(e.length>n){for(;" "!==e[n]&&0<--n;);return e.substring(0,n)+"..."}return e}(e.text.replace(s,c),400)}):{})))}});(n=a.stack_).push.apply(n,[function(){return a.list_.appendChild(i)}].concat(o))});var o=this.el_.parentNode;if(!(o instanceof HTMLElement))throw new ReferenceError;for(;this.stack_.length&&o.offsetHeight>=o.scrollHeight-16;)this.stack_.shift()();var l=this.list_.querySelectorAll("[data-md-rel=anchor]");switch(Array.prototype.forEach.call(l,function(r){["click","keydown"].forEach(function(n){r.addEventListener(n,function(e){if("keydown"!==n||13===e.keyCode){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.checked&&(t.checked=!1,t.dispatchEvent(new CustomEvent("change"))),e.preventDefault(),setTimeout(function(){document.location.href=r.href},100)}})})}),r.size){case 0:this.meta_.textContent=this.message_.none;break;case 1:this.meta_.textContent=this.message_.one;break;default:this.meta_.textContent=this.message_.other.replace("#",r.size)}}}else{var u=function(e){a.docs_=e.reduce(function(e,t){var n,r,i,o=t.location.split("#"),a=o[0],s=o[1];return t.text=(n=t.text,r=document.createTextNode(n),(i=document.createElement("p")).appendChild(r),i.innerHTML),s&&(t.parent=e.get(a),t.parent&&!t.parent.done&&(t.parent.title=t.title,t.parent.text=t.text,t.parent.done=!0)),t.text=t.text.replace(/\n/g," ").replace(/\s+/g," ").replace(/\s+([,.:;!?])/g,function(e,t){return t}),t.parent&&t.parent.title===t.title||e.set(t.location,t),e},new Map);var i=a.docs_,o=a.lang_;a.stack_=[],a.index_=d()(function(){var e,t=this,n={"search.pipeline.trimmer":d.a.trimmer,"search.pipeline.stopwords":d.a.stopWordFilter},r=Object.keys(n).reduce(function(e,t){return h(t).match(/^false$/i)||e.push(n[t]),e},[]);this.pipeline.reset(),r&&(e=this.pipeline).add.apply(e,r),1===o.length&&"en"!==o[0]&&d.a[o[0]]?this.use(d.a[o[0]]):1=t.scrollHeight-16;)a.stack_.splice(0,10).forEach(function(e){return e()})})};setTimeout(function(){return"function"==typeof a.data_?a.data_().then(u):u(a.data_)},250)}},e}()}).call(this,r(3))},function(e,n,r){"use strict";(function(t){r.d(n,"a",function(){return e});var e=function(){function e(e){var t="string"==typeof e?document.querySelector(e):e;if(!(t instanceof HTMLElement))throw new ReferenceError;this.el_=t}return e.prototype.initialize=function(e){e.length&&this.el_.children.length&&this.el_.children[this.el_.children.length-1].appendChild(t.createElement("ul",{class:"md-source__facts"},e.map(function(e){return t.createElement("li",{class:"md-source__fact"},e)}))),this.el_.dataset.mdState="done"},e}()}).call(this,r(3))},,,function(e,n,c){"use strict";c.r(n),function(o){c.d(n,"app",function(){return t});c(14),c(15),c(16),c(17),c(18),c(19),c(20);var r=c(2),e=c(5),a=c.n(e),i=c(0);window.Promise=window.Promise||r.a;var s=function(e){var t=document.getElementsByName("lang:"+e)[0];if(!(t instanceof HTMLMetaElement))throw new ReferenceError;return t.content};var t={initialize:function(t){new i.a.Event.Listener(document,"DOMContentLoaded",function(){if(!(document.body instanceof HTMLElement))throw new ReferenceError;Modernizr.addTest("ios",function(){return!!navigator.userAgent.match(/(iPad|iPhone|iPod)/g)});var e=document.querySelectorAll("table:not([class])");if(Array.prototype.forEach.call(e,function(e){var t=o.createElement("div",{class:"md-typeset__scrollwrap"},o.createElement("div",{class:"md-typeset__table"}));e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t),t.children[0].appendChild(e)}),a.a.isSupported()){var t=document.querySelectorAll(".codehilite > pre, pre > code");Array.prototype.forEach.call(t,function(e,t){var n="__code_"+t,r=o.createElement("button",{class:"md-clipboard",title:s("clipboard.copy"),"data-clipboard-target":"#"+n+" pre, #"+n+" code"},o.createElement("span",{class:"md-clipboard__message"})),i=e.parentNode;i.id=n,i.insertBefore(r,e)}),new a.a(".md-clipboard").on("success",function(e){var t=e.trigger.querySelector(".md-clipboard__message");if(!(t instanceof HTMLElement))throw new ReferenceError;e.clearSelection(),t.dataset.mdTimer&&clearTimeout(parseInt(t.dataset.mdTimer,10)),t.classList.add("md-clipboard__message--active"),t.innerHTML=s("clipboard.copied"),t.dataset.mdTimer=setTimeout(function(){t.classList.remove("md-clipboard__message--active"),t.dataset.mdTimer=""},2e3).toString()})}if(!Modernizr.details){var n=document.querySelectorAll("details > summary");Array.prototype.forEach.call(n,function(e){e.addEventListener("click",function(e){var t=e.target.parentNode;t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})})}var r=function(){if(document.location.hash){var e=document.getElementById(document.location.hash.substring(1));if(!e)return;for(var t=e.parentNode;t&&!(t instanceof HTMLDetailsElement);)t=t.parentNode;if(t&&!t.open){t.open=!0;var n=location.hash;location.hash=" ",location.hash=n}}};if(window.addEventListener("hashchange",r),r(),Modernizr.ios){var i=document.querySelectorAll("[data-md-scrollfix]");Array.prototype.forEach.call(i,function(t){t.addEventListener("touchstart",function(){var e=t.scrollTop;0===e?t.scrollTop=1:e+t.offsetHeight===t.scrollHeight&&(t.scrollTop=e-1)})})}}).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Shadow("[data-md-component=container]","[data-md-component=header]")).listen(),new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Header.Title("[data-md-component=title]",".md-typeset h1")).listen(),document.querySelector("[data-md-component=hero]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=hero]")).listen(),document.querySelector("[data-md-component=tabs]")&&new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Tabs.Toggle("[data-md-component=tabs]")).listen(),new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=navigation]","[data-md-component=header]"))),document.querySelector("[data-md-component=toc]")&&new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,["scroll","resize","orientationchange"],new i.a.Sidebar.Position("[data-md-component=toc]","[data-md-component=header]"))),new i.a.Event.MatchMedia("(min-width: 960px)",new i.a.Event.Listener(window,"scroll",new i.a.Nav.Blur("[data-md-component=toc] .md-nav__link")));var e=document.querySelectorAll("[data-md-component=collapsible]");Array.prototype.forEach.call(e,function(e){new i.a.Event.MatchMedia("(min-width: 1220px)",new i.a.Event.Listener(e.previousElementSibling,"click",new i.a.Nav.Collapse(e)))}),new i.a.Event.MatchMedia("(max-width: 1219px)",new i.a.Event.Listener("[data-md-component=navigation] [data-md-toggle]","change",new i.a.Nav.Scrolling("[data-md-component=navigation] nav"))),document.querySelector("[data-md-component=search]")&&(new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-toggle=search]","change",new i.a.Search.Lock("[data-md-toggle=search]"))),new i.a.Event.Listener("[data-md-component=query]",["focus","keyup","change"],new i.a.Search.Result("[data-md-component=result]",function(){return fetch(t.url.base+"/search/search_index.json",{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return e.docs.map(function(e){return e.location=t.url.base+"/"+e.location,e})})})).listen(),new i.a.Event.Listener("[data-md-component=reset]","click",function(){setTimeout(function(){var e=document.querySelector("[data-md-component=query]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.focus()},10)}).listen(),new i.a.Event.Listener("[data-md-toggle=search]","change",function(e){setTimeout(function(e){if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t.focus()}},400,e.target)}).listen(),new i.a.Event.Listener("[data-md-component=query]","focus",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked||(e.checked=!0,e.dispatchEvent(new CustomEvent("change")))}).listen(),new i.a.Event.Listener(window,"keydown",function(e){var t=document.querySelector("[data-md-toggle=search]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;var n=document.querySelector("[data-md-component=query]");if(!(n instanceof HTMLInputElement))throw new ReferenceError;if(!(document.activeElement instanceof HTMLElement&&document.activeElement.isContentEditable||e.metaKey||e.ctrlKey))if(t.checked){if(13===e.keyCode){if(n===document.activeElement){e.preventDefault();var r=document.querySelector("[data-md-component=search] [href][data-md-state=active]");r instanceof HTMLLinkElement&&(window.location=r.getAttribute("href"),t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur())}}else if(9===e.keyCode||27===e.keyCode)t.checked=!1,t.dispatchEvent(new CustomEvent("change")),n.blur();else if(-1!==[8,37,39].indexOf(e.keyCode))n!==document.activeElement&&n.focus();else if(-1!==[38,40].indexOf(e.keyCode)){var i=e.keyCode,o=Array.prototype.slice.call(document.querySelectorAll("[data-md-component=query], [data-md-component=search] [href]")),a=o.find(function(e){if(!(e instanceof HTMLElement))throw new ReferenceError;return"active"===e.dataset.mdState});a&&(a.dataset.mdState="");var s=Math.max(0,(o.indexOf(a)+o.length+(38===i?-1:1))%o.length);return o[s]&&(o[s].dataset.mdState="active",o[s].focus()),e.preventDefault(),e.stopPropagation(),!1}}else if(document.activeElement&&!document.activeElement.form){if("TEXTAREA"===document.activeElement.tagName||"INPUT"===document.activeElement.tagName)return;70!==e.keyCode&&83!==e.keyCode||(n.focus(),e.preventDefault())}}).listen(),new i.a.Event.Listener(window,"keypress",function(){var e=document.querySelector("[data-md-toggle=search]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;if(e.checked){var t=document.querySelector("[data-md-component=query]");if(!(t instanceof HTMLInputElement))throw new ReferenceError;t!==document.activeElement&&t.focus()}}).listen()),new i.a.Event.Listener(document.body,"keydown",function(e){if(9===e.keyCode){var t=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[for]:not([tabindex])");Array.prototype.forEach.call(t,function(e){e.offsetHeight&&(e.tabIndex=0)})}}).listen(),new i.a.Event.Listener(document.body,"mousedown",function(){var e=document.querySelectorAll("[data-md-component=navigation] .md-nav__link[tabindex]");Array.prototype.forEach.call(e,function(e){e.removeAttribute("tabIndex")})}).listen(),document.body.addEventListener("click",function(){"tabbing"===document.body.dataset.mdState&&(document.body.dataset.mdState="")}),new i.a.Event.MatchMedia("(max-width: 959px)",new i.a.Event.Listener("[data-md-component=navigation] [href^='#']","click",function(){var e=document.querySelector("[data-md-toggle=drawer]");if(!(e instanceof HTMLInputElement))throw new ReferenceError;e.checked&&(e.checked=!1,e.dispatchEvent(new CustomEvent("change")))})),function(){var e=document.querySelector("[data-md-source]");if(!e)return r.a.resolve([]);if(!(e instanceof HTMLAnchorElement))throw new ReferenceError;switch(e.dataset.mdSource){case"github":return new i.a.Source.Adapter.GitHub(e).fetch();default:return r.a.resolve([])}}().then(function(t){var e=document.querySelectorAll("[data-md-source]");Array.prototype.forEach.call(e,function(e){new i.a.Source.Repository(e).initialize(t)})});var n=function(){var e=document.querySelectorAll("details");Array.prototype.forEach.call(e,function(e){e.setAttribute("open","")})};new i.a.Event.MatchMedia("print",{listen:n,unlisten:function(){}}),window.onbeforeprint=n}}}.call(this,c(3))},function(e,t,n){e.exports=n.p+"assets/images/icons/bitbucket.1b09e088.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/github.f0b8504a.svg"},function(e,t,n){e.exports=n.p+"assets/images/icons/gitlab.6dd19c00.svg"},function(e,t){e.exports="/home/travis/build/squidfunk/mkdocs-material/material/application.30686662.css"},function(e,t){e.exports="/home/travis/build/squidfunk/mkdocs-material/material/application-palette.a8b3c06d.css"},function(e,t){!function(){if("undefined"!=typeof window)try{var e=new window.CustomEvent("test",{cancelable:!0});if(e.preventDefault(),!0!==e.defaultPrevented)throw new Error("Could not prevent default")}catch(e){var t=function(e,t){var n,r;return(t=t||{}).bubbles=!!t.bubbles,t.cancelable=!!t.cancelable,(n=document.createEvent("CustomEvent")).initCustomEvent(e,t.bubbles,t.cancelable,t.detail),r=n.preventDefault,n.preventDefault=function(){r.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(e){this.defaultPrevented=!0}},n};t.prototype=window.Event.prototype,window.CustomEvent=t}}()},function(e,t,n){window.fetch||(window.fetch=n(7).default||n(7))},function(e,i,o){(function(e){var t=void 0!==e&&e||"undefined"!=typeof self&&self||window,n=Function.prototype.apply;function r(e,t){this._id=e,this._clearFn=t}i.setTimeout=function(){return new r(n.call(setTimeout,t,arguments),clearTimeout)},i.setInterval=function(){return new r(n.call(setInterval,t,arguments),clearInterval)},i.clearTimeout=i.clearInterval=function(e){e&&e.close()},r.prototype.unref=r.prototype.ref=function(){},r.prototype.close=function(){this._clearFn.call(t,this._id)},i.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},i.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},i._unrefActive=i.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;0<=t&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout()},t))},o(22),i.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,i.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,o(4))},function(e,t,n){(function(e,p){!function(n,r){"use strict";if(!n.setImmediate){var i,o,t,a,e,s=1,c={},l=!1,u=n.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(n);f=f&&f.setTimeout?f:n,i="[object process]"==={}.toString.call(n.process)?function(e){p.nextTick(function(){h(e)})}:function(){if(n.postMessage&&!n.importScripts){var e=!0,t=n.onmessage;return n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",e=function(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(a)&&h(+e.data.slice(a.length))},n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),function(e){n.postMessage(a+e,"*")}):n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){h(e.data)},function(e){t.port2.postMessage(e)}):u&&"onreadystatechange"in u.createElement("script")?(o=u.documentElement,function(e){var t=u.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):function(e){setTimeout(h,0,e)},f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n=this.length)return D.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},D.QueryLexer.prototype.width=function(){return this.pos-this.start},D.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},D.QueryLexer.prototype.backup=function(){this.pos-=1},D.QueryLexer.prototype.acceptDigitRun=function(){for(var e,t;47<(t=(e=this.next()).charCodeAt(0))&&t<58;);e!=D.QueryLexer.EOS&&this.backup()},D.QueryLexer.prototype.more=function(){return this.pos=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.find_among_b(o,4)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e)}this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r=c.cursor;return function(){var e,r=c.cursor+3;if(t=c.limit,0<=r&&r<=c.limit){for(i=r;;){if(e=c.cursor,c.in_grouping(d,97,248)){c.cursor=e;break}if((c.cursor=e)>=c.limit)return;c.cursor++}for(;!c.out_grouping(d,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(t=c.cursor)=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(s,32),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:c.in_grouping_b(u,97,229)&&c.slice_del()}}(),c.cursor=c.limit,l(),c.cursor=c.limit,function(){var e,r,i,n=c.limit-c.cursor;if(c.ket=c.cursor,c.eq_s_b(2,"st")&&(c.bra=c.cursor,c.eq_s_b(2,"ig")&&c.slice_del()),c.cursor=c.limit-n,c.cursor>=t&&(r=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,e=c.find_among_b(a,5),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del(),i=c.limit-c.cursor,l(),c.cursor=c.limit-i;break;case 2:c.slice_from("løs")}}(),c.cursor=c.limit,c.cursor>=t&&(e=c.limit_backward,c.limit_backward=t,c.ket=c.cursor,c.out_grouping_b(d,97,248)?(c.bra=c.cursor,n=c.slice_to(n),c.limit_backward=e,c.eq_v_b(n)&&c.slice_del()):c.limit_backward=e),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.de.js b/assets/javascripts/lunr/lunr.de.js new file mode 100644 index 00000000..73e55eb0 --- /dev/null +++ b/assets/javascripts/lunr/lunr.de.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `German` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var _,p,r;e.de=function(){this.pipeline.reset(),this.pipeline.add(e.de.trimmer,e.de.stopWordFilter,e.de.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.de.stemmer))},e.de.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.de.trimmer=e.trimmerSupport.generateTrimmer(e.de.wordCharacters),e.Pipeline.registerFunction(e.de.trimmer,"trimmer-de"),e.de.stemmer=(_=e.stemmerSupport.Among,p=e.stemmerSupport.SnowballProgram,r=new function(){var r,n,i,s=[new _("",-1,6),new _("U",0,2),new _("Y",0,1),new _("ä",0,3),new _("ö",0,4),new _("ü",0,5)],o=[new _("e",-1,2),new _("em",-1,1),new _("en",-1,2),new _("ern",-1,1),new _("er",-1,1),new _("s",-1,3),new _("es",5,2)],c=[new _("en",-1,1),new _("er",-1,1),new _("st",-1,2),new _("est",2,1)],u=[new _("ig",-1,1),new _("lich",-1,1)],a=[new _("end",-1,1),new _("ig",-1,2),new _("ung",-1,1),new _("lich",-1,3),new _("isch",-1,2),new _("ik",-1,2),new _("heit",-1,3),new _("keit",-1,4)],t=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32,8],d=[117,30,5],l=[117,30,4],m=new p;function h(e,r,n){return!(!m.eq_s(1,e)||(m.ket=m.cursor,!m.in_grouping(t,97,252)))&&(m.slice_from(r),m.cursor=n,!0)}function w(){for(;!m.in_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}for(;!m.out_grouping(t,97,252);){if(m.cursor>=m.limit)return!0;m.cursor++}return!1}function f(){return i<=m.cursor}function b(){return n<=m.cursor}this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e=m.cursor;return function(){for(var e,r,n,i,s=m.cursor;;)if(e=m.cursor,m.bra=e,m.eq_s(1,"ß"))m.ket=m.cursor,m.slice_from("ss");else{if(e>=m.limit)break;m.cursor=e+1}for(m.cursor=s;;)for(r=m.cursor;;){if(n=m.cursor,m.in_grouping(t,97,252)){if(i=m.cursor,m.bra=i,h("u","U",n))break;if(m.cursor=i,h("y","Y",n))break}if(n>=m.limit)return m.cursor=r;m.cursor=n+1}}(),m.cursor=e,function(){i=m.limit,n=i;var e=m.cursor+3;0<=e&&e<=m.limit&&(r=e,w()||((i=m.cursor)=m.limit)return;m.cursor++}}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.de.stemmer,"stemmer-de"),e.de.stopWordFilter=e.generateStopWordFilter("aber alle allem allen aller alles als also am an ander andere anderem anderen anderer anderes anderm andern anderr anders auch auf aus bei bin bis bist da damit dann das dasselbe dazu daß dein deine deinem deinen deiner deines dem demselben den denn denselben der derer derselbe derselben des desselben dessen dich die dies diese dieselbe dieselben diesem diesen dieser dieses dir doch dort du durch ein eine einem einen einer eines einig einige einigem einigen einiger einiges einmal er es etwas euch euer eure eurem euren eurer eures für gegen gewesen hab habe haben hat hatte hatten hier hin hinter ich ihm ihn ihnen ihr ihre ihrem ihren ihrer ihres im in indem ins ist jede jedem jeden jeder jedes jene jenem jenen jener jenes jetzt kann kein keine keinem keinen keiner keines können könnte machen man manche manchem manchen mancher manches mein meine meinem meinen meiner meines mich mir mit muss musste nach nicht nichts noch nun nur ob oder ohne sehr sein seine seinem seinen seiner seines selbst sich sie sind so solche solchem solchen solcher solches soll sollte sondern sonst um und uns unse unsem unsen unser unses unter viel vom von vor war waren warst was weg weil weiter welche welchem welchen welcher welches wenn werde werden wie wieder will wir wird wirst wo wollen wollte während würde würden zu zum zur zwar zwischen über".split(" ")),e.Pipeline.registerFunction(e.de.stopWordFilter,"stopWordFilter-de")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.du.js b/assets/javascripts/lunr/lunr.du.js new file mode 100644 index 00000000..e9c67299 --- /dev/null +++ b/assets/javascripts/lunr/lunr.du.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Dutch` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,q,r;console.warn('[Lunr Languages] Please use the "nl" instead of the "du". The "nl" code is the standard code for Dutch language, and "du" will be removed in the next major versions.'),e.du=function(){this.pipeline.reset(),this.pipeline.add(e.du.trimmer,e.du.stopWordFilter,e.du.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.du.stemmer))},e.du.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.du.trimmer=e.trimmerSupport.generateTrimmer(e.du.wordCharacters),e.Pipeline.registerFunction(e.du.trimmer,"trimmer-du"),e.du.stemmer=(v=e.stemmerSupport.Among,q=e.stemmerSupport.SnowballProgram,r=new function(){var r,i,u,o=[new v("",-1,6),new v("á",0,1),new v("ä",0,1),new v("é",0,2),new v("ë",0,2),new v("í",0,3),new v("ï",0,3),new v("ó",0,4),new v("ö",0,4),new v("ú",0,5),new v("ü",0,5)],n=[new v("",-1,3),new v("I",0,2),new v("Y",0,1)],t=[new v("dd",-1,-1),new v("kk",-1,-1),new v("tt",-1,-1)],c=[new v("ene",-1,2),new v("se",-1,3),new v("en",-1,2),new v("heden",2,1),new v("s",-1,3)],a=[new v("end",-1,1),new v("ig",-1,2),new v("ing",-1,1),new v("lijk",-1,3),new v("baar",-1,4),new v("bar",-1,5)],l=[new v("aa",-1,-1),new v("ee",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1)],m=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],d=[1,0,0,17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],f=[17,67,16,1,0,0,0,0,0,0,0,0,0,0,0,0,128],_=new q;function s(e){return(_.cursor=e)>=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return r<=_.cursor}function g(){var e=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-e,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var e;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.slice_del(),u=!0,g())))}function k(){var e;b()&&(e=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-e,_.eq_s_b(3,"gem")||(_.cursor=_.limit-e,_.slice_del(),g())))}this.setCurrent=function(e){_.setCurrent(e)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var e=_.cursor;return function(){for(var e,r,i,n=_.cursor;;){if(_.bra=_.cursor,e=_.find_among(o,11))switch(_.ket=_.cursor,e){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(r=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=r);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=r;else if(s(r))break}else if(s(r))break}(),_.cursor=e,i=_.limit,r=i,w()||((i=_.cursor)<3&&(i=3),w()||(r=_.cursor)),_.limit_backward=e,_.cursor=_.limit,function(){var e,r,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,e=_.find_among_b(c,5))switch(_.bra=_.cursor,e){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(r=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-r,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,e=_.find_among_b(a,6))switch(_.bra=_.cursor,e){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var e;;)if(_.bra=_.cursor,e=_.find_among(n,3))switch(_.ket=_.cursor,e){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.du.stemmer,"stemmer-du"),e.du.stopWordFilter=e.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),e.Pipeline.registerFunction(e.du.stopWordFilter,"stopWordFilter-du")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.es.js b/assets/javascripts/lunr/lunr.es.js new file mode 100644 index 00000000..2918bd19 --- /dev/null +++ b/assets/javascripts/lunr/lunr.es.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Spanish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,s){"function"==typeof define&&define.amd?define(s):"object"==typeof exports?module.exports=s():s()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var C,P,s;e.es=function(){this.pipeline.reset(),this.pipeline.add(e.es.trimmer,e.es.stopWordFilter,e.es.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.es.stemmer))},e.es.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.es.trimmer=e.trimmerSupport.generateTrimmer(e.es.wordCharacters),e.Pipeline.registerFunction(e.es.trimmer,"trimmer-es"),e.es.stemmer=(C=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,s=new function(){var r,n,i,a=[new C("",-1,6),new C("á",0,1),new C("é",0,2),new C("í",0,3),new C("ó",0,4),new C("ú",0,5)],t=[new C("la",-1,-1),new C("sela",0,-1),new C("le",-1,-1),new C("me",-1,-1),new C("se",-1,-1),new C("lo",-1,-1),new C("selo",5,-1),new C("las",-1,-1),new C("selas",7,-1),new C("les",-1,-1),new C("los",-1,-1),new C("selos",10,-1),new C("nos",-1,-1)],o=[new C("ando",-1,6),new C("iendo",-1,6),new C("yendo",-1,7),new C("ándo",-1,2),new C("iéndo",-1,1),new C("ar",-1,6),new C("er",-1,6),new C("ir",-1,6),new C("ár",-1,3),new C("ér",-1,4),new C("ír",-1,5)],s=[new C("ic",-1,-1),new C("ad",-1,-1),new C("os",-1,-1),new C("iv",-1,1)],u=[new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,1)],w=[new C("ic",-1,1),new C("abil",-1,1),new C("iv",-1,1)],c=[new C("ica",-1,1),new C("ancia",-1,2),new C("encia",-1,5),new C("adora",-1,2),new C("osa",-1,1),new C("ista",-1,1),new C("iva",-1,9),new C("anza",-1,1),new C("logía",-1,3),new C("idad",-1,8),new C("able",-1,1),new C("ible",-1,1),new C("ante",-1,2),new C("mente",-1,7),new C("amente",13,6),new C("ación",-1,2),new C("ución",-1,4),new C("ico",-1,1),new C("ismo",-1,1),new C("oso",-1,1),new C("amiento",-1,1),new C("imiento",-1,1),new C("ivo",-1,9),new C("ador",-1,2),new C("icas",-1,1),new C("ancias",-1,2),new C("encias",-1,5),new C("adoras",-1,2),new C("osas",-1,1),new C("istas",-1,1),new C("ivas",-1,9),new C("anzas",-1,1),new C("logías",-1,3),new C("idades",-1,8),new C("ables",-1,1),new C("ibles",-1,1),new C("aciones",-1,2),new C("uciones",-1,4),new C("adores",-1,2),new C("antes",-1,2),new C("icos",-1,1),new C("ismos",-1,1),new C("osos",-1,1),new C("amientos",-1,1),new C("imientos",-1,1),new C("ivos",-1,9)],m=[new C("ya",-1,1),new C("ye",-1,1),new C("yan",-1,1),new C("yen",-1,1),new C("yeron",-1,1),new C("yendo",-1,1),new C("yo",-1,1),new C("yas",-1,1),new C("yes",-1,1),new C("yais",-1,1),new C("yamos",-1,1),new C("yó",-1,1)],l=[new C("aba",-1,2),new C("ada",-1,2),new C("ida",-1,2),new C("ara",-1,2),new C("iera",-1,2),new C("ía",-1,2),new C("aría",5,2),new C("ería",5,2),new C("iría",5,2),new C("ad",-1,2),new C("ed",-1,2),new C("id",-1,2),new C("ase",-1,2),new C("iese",-1,2),new C("aste",-1,2),new C("iste",-1,2),new C("an",-1,2),new C("aban",16,2),new C("aran",16,2),new C("ieran",16,2),new C("ían",16,2),new C("arían",20,2),new C("erían",20,2),new C("irían",20,2),new C("en",-1,1),new C("asen",24,2),new C("iesen",24,2),new C("aron",-1,2),new C("ieron",-1,2),new C("arán",-1,2),new C("erán",-1,2),new C("irán",-1,2),new C("ado",-1,2),new C("ido",-1,2),new C("ando",-1,2),new C("iendo",-1,2),new C("ar",-1,2),new C("er",-1,2),new C("ir",-1,2),new C("as",-1,2),new C("abas",39,2),new C("adas",39,2),new C("idas",39,2),new C("aras",39,2),new C("ieras",39,2),new C("ías",39,2),new C("arías",45,2),new C("erías",45,2),new C("irías",45,2),new C("es",-1,1),new C("ases",49,2),new C("ieses",49,2),new C("abais",-1,2),new C("arais",-1,2),new C("ierais",-1,2),new C("íais",-1,2),new C("aríais",55,2),new C("eríais",55,2),new C("iríais",55,2),new C("aseis",-1,2),new C("ieseis",-1,2),new C("asteis",-1,2),new C("isteis",-1,2),new C("áis",-1,2),new C("éis",-1,1),new C("aréis",64,2),new C("eréis",64,2),new C("iréis",64,2),new C("ados",-1,2),new C("idos",-1,2),new C("amos",-1,2),new C("ábamos",70,2),new C("áramos",70,2),new C("iéramos",70,2),new C("íamos",70,2),new C("aríamos",74,2),new C("eríamos",74,2),new C("iríamos",74,2),new C("emos",-1,1),new C("aremos",78,2),new C("eremos",78,2),new C("iremos",78,2),new C("ásemos",78,2),new C("iésemos",78,2),new C("imos",-1,2),new C("arás",-1,2),new C("erás",-1,2),new C("irás",-1,2),new C("ís",-1,2),new C("ará",-1,2),new C("erá",-1,2),new C("irá",-1,2),new C("aré",-1,2),new C("eré",-1,2),new C("iré",-1,2),new C("ió",-1,2)],d=[new C("a",-1,1),new C("e",-1,2),new C("o",-1,1),new C("os",-1,1),new C("á",-1,1),new C("é",-1,2),new C("í",-1,1),new C("ó",-1,1)],b=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,4,10],f=new P;function _(){if(f.out_grouping(b,97,252)){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}return!1}return!0}function h(){var e,s=f.cursor;if(function(){if(f.in_grouping(b,97,252)){var e=f.cursor;if(_()){if(f.cursor=e,!f.in_grouping(b,97,252))return!0;for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!0;f.cursor++}}return!1}return!0}()){if(f.cursor=s,!f.out_grouping(b,97,252))return;if(e=f.cursor,_()){if(f.cursor=e,!f.in_grouping(b,97,252)||f.cursor>=f.limit)return;f.cursor++}}i=f.cursor}function v(){for(;!f.in_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}for(;!f.out_grouping(b,97,252);){if(f.cursor>=f.limit)return!1;f.cursor++}return!0}function p(){return i<=f.cursor}function g(){return r<=f.cursor}function k(e,s){if(!g())return!0;f.slice_del(),f.ket=f.cursor;var r=f.find_among_b(e,s);return r&&(f.bra=f.cursor,1==r&&g()&&f.slice_del()),!1}function y(e){return!g()||(f.slice_del(),f.ket=f.cursor,f.eq_s_b(2,e)&&(f.bra=f.cursor,g()&&f.slice_del()),!1)}function q(){var e;if(f.ket=f.cursor,e=f.find_among_b(c,46)){switch(f.bra=f.cursor,e){case 1:if(!g())return!1;f.slice_del();break;case 2:if(y("ic"))return!1;break;case 3:if(!g())return!1;f.slice_from("log");break;case 4:if(!g())return!1;f.slice_from("u");break;case 5:if(!g())return!1;f.slice_from("ente");break;case 6:if(!(n<=f.cursor))return!1;f.slice_del(),f.ket=f.cursor,(e=f.find_among_b(s,4))&&(f.bra=f.cursor,g()&&(f.slice_del(),1==e&&(f.ket=f.cursor,f.eq_s_b(2,"at")&&(f.bra=f.cursor,g()&&f.slice_del()))));break;case 7:if(k(u,3))return!1;break;case 8:if(k(w,3))return!1;break;case 9:if(y("at"))return!1}return!0}return!1}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e,s=f.cursor;return e=f.cursor,i=f.limit,r=n=i,h(),f.cursor=e,v()&&(n=f.cursor,v()&&(r=f.cursor)),f.limit_backward=s,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,f.find_among_b(t,13)&&(f.bra=f.cursor,(e=f.find_among_b(o,11))&&p()))switch(e){case 1:f.bra=f.cursor,f.slice_from("iendo");break;case 2:f.bra=f.cursor,f.slice_from("ando");break;case 3:f.bra=f.cursor,f.slice_from("ar");break;case 4:f.bra=f.cursor,f.slice_from("er");break;case 5:f.bra=f.cursor,f.slice_from("ir");break;case 6:f.slice_del();break;case 7:f.eq_s_b(1,"u")&&f.slice_del()}}(),f.cursor=f.limit,q()||(f.cursor=f.limit,function(){var e,s;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(m,12),f.limit_backward=s,e)){if(f.bra=f.cursor,1==e){if(!f.eq_s_b(1,"u"))return!1;f.slice_del()}return!0}return!1}()||(f.cursor=f.limit,function(){var e,s,r,n;if(f.cursor>=i&&(s=f.limit_backward,f.limit_backward=i,f.ket=f.cursor,e=f.find_among_b(l,96),f.limit_backward=s,e))switch(f.bra=f.cursor,e){case 1:r=f.limit-f.cursor,f.eq_s_b(1,"u")?(n=f.limit-f.cursor,f.eq_s_b(1,"g")?f.cursor=f.limit-n:f.cursor=f.limit-r):f.cursor=f.limit-r,f.bra=f.cursor;case 2:f.slice_del()}}())),f.cursor=f.limit,function(){var e,s;if(f.ket=f.cursor,e=f.find_among_b(d,8))switch(f.bra=f.cursor,e){case 1:p()&&f.slice_del();break;case 2:p()&&(f.slice_del(),f.ket=f.cursor,f.eq_s_b(1,"u")&&(f.bra=f.cursor,s=f.limit-f.cursor,f.eq_s_b(1,"g")&&(f.cursor=f.limit-s,p()&&f.slice_del())))}}(),f.cursor=f.limit_backward,function(){for(var e;;){if(f.bra=f.cursor,e=f.find_among(a,6))switch(f.ket=f.cursor,e){case 1:f.slice_from("a");continue;case 2:f.slice_from("e");continue;case 3:f.slice_from("i");continue;case 4:f.slice_from("o");continue;case 5:f.slice_from("u");continue;case 6:if(f.cursor>=f.limit)break;f.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.es.stemmer,"stemmer-es"),e.es.stopWordFilter=e.generateStopWordFilter("a al algo algunas algunos ante antes como con contra cual cuando de del desde donde durante e el ella ellas ellos en entre era erais eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estamos estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos ha habida habidas habido habidos habiendo habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías han has hasta hay haya hayamos hayan hayas hayáis he hemos hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo la las le les lo los me mi mis mucho muchos muy más mí mía mías mío míos nada ni no nos nosotras nosotros nuestra nuestras nuestro nuestros o os otra otras otro otros para pero poco por porque que quien quienes qué se sea seamos sean seas seremos será serán serás seré seréis sería seríais seríamos serían serías seáis sido siendo sin sobre sois somos son soy su sus suya suyas suyo suyos sí también tanto te tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened tenemos tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías ti tiene tienen tienes todo todos tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú un una uno unos vosotras vosotros vuestra vuestras vuestro vuestros y ya yo él éramos".split(" ")),e.Pipeline.registerFunction(e.es.stopWordFilter,"stopWordFilter-es")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.fi.js b/assets/javascripts/lunr/lunr.fi.js new file mode 100644 index 00000000..f34d10e0 --- /dev/null +++ b/assets/javascripts/lunr/lunr.fi.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Finnish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(i,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():e()(i.lunr)}(this,function(){return function(i){if(void 0===i)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===i.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var v,C,e;i.fi=function(){this.pipeline.reset(),this.pipeline.add(i.fi.trimmer,i.fi.stopWordFilter,i.fi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(i.fi.stemmer))},i.fi.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",i.fi.trimmer=i.trimmerSupport.generateTrimmer(i.fi.wordCharacters),i.Pipeline.registerFunction(i.fi.trimmer,"trimmer-fi"),i.fi.stemmer=(v=i.stemmerSupport.Among,C=i.stemmerSupport.SnowballProgram,e=new function(){var n,t,l,o,r=[new v("pa",-1,1),new v("sti",-1,2),new v("kaan",-1,1),new v("han",-1,1),new v("kin",-1,1),new v("hän",-1,1),new v("kään",-1,1),new v("ko",-1,1),new v("pä",-1,1),new v("kö",-1,1)],s=[new v("lla",-1,-1),new v("na",-1,-1),new v("ssa",-1,-1),new v("ta",-1,-1),new v("lta",3,-1),new v("sta",3,-1)],a=[new v("llä",-1,-1),new v("nä",-1,-1),new v("ssä",-1,-1),new v("tä",-1,-1),new v("ltä",3,-1),new v("stä",3,-1)],u=[new v("lle",-1,-1),new v("ine",-1,-1)],c=[new v("nsa",-1,3),new v("mme",-1,3),new v("nne",-1,3),new v("ni",-1,2),new v("si",-1,1),new v("an",-1,4),new v("en",-1,6),new v("än",-1,5),new v("nsä",-1,3)],i=[new v("aa",-1,-1),new v("ee",-1,-1),new v("ii",-1,-1),new v("oo",-1,-1),new v("uu",-1,-1),new v("ää",-1,-1),new v("öö",-1,-1)],m=[new v("a",-1,8),new v("lla",0,-1),new v("na",0,-1),new v("ssa",0,-1),new v("ta",0,-1),new v("lta",4,-1),new v("sta",4,-1),new v("tta",4,9),new v("lle",-1,-1),new v("ine",-1,-1),new v("ksi",-1,-1),new v("n",-1,7),new v("han",11,1),new v("den",11,-1,q),new v("seen",11,-1,j),new v("hen",11,2),new v("tten",11,-1,q),new v("hin",11,3),new v("siin",11,-1,q),new v("hon",11,4),new v("hän",11,5),new v("hön",11,6),new v("ä",-1,8),new v("llä",22,-1),new v("nä",22,-1),new v("ssä",22,-1),new v("tä",22,-1),new v("ltä",26,-1),new v("stä",26,-1),new v("ttä",26,9)],w=[new v("eja",-1,-1),new v("mma",-1,1),new v("imma",1,-1),new v("mpa",-1,1),new v("impa",3,-1),new v("mmi",-1,1),new v("immi",5,-1),new v("mpi",-1,1),new v("impi",7,-1),new v("ejä",-1,-1),new v("mmä",-1,1),new v("immä",10,-1),new v("mpä",-1,1),new v("impä",12,-1)],_=[new v("i",-1,-1),new v("j",-1,-1)],k=[new v("mma",-1,1),new v("imma",0,-1)],b=[17,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],e=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],f=[17,97,24,1,0,0,0,0,0,0,0,0,0,0,0,0,8,0,32],h=new C;function p(){for(var i;i=h.cursor,!h.in_grouping(d,97,246);){if((h.cursor=i)>=h.limit)return!0;h.cursor++}for(h.cursor=i;!h.out_grouping(d,97,246);){if(h.cursor>=h.limit)return!0;h.cursor++}return!1}function g(){var i,e;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(r,10)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.in_grouping_b(f,97,246))return;break;case 2:if(!(l<=h.cursor))return}h.slice_del()}else h.limit_backward=e}function j(){return h.find_among_b(i,7)}function q(){return h.eq_s_b(1,"i")&&h.in_grouping_b(e,97,246)}this.setCurrent=function(i){h.setCurrent(i)},this.getCurrent=function(){return h.getCurrent()},this.stem=function(){var i,e=h.cursor;return o=h.limit,l=o,p()||(o=h.cursor,p()||(l=h.cursor)),n=!1,h.limit_backward=e,h.cursor=h.limit,g(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(c,9))switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:r=h.limit-h.cursor,h.eq_s_b(1,"k")||(h.cursor=h.limit-r,h.slice_del());break;case 2:h.slice_del(),h.ket=h.cursor,h.eq_s_b(3,"kse")&&(h.bra=h.cursor,h.slice_from("ksi"));break;case 3:h.slice_del();break;case 4:h.find_among_b(s,6)&&h.slice_del();break;case 5:h.find_among_b(a,6)&&h.slice_del();break;case 6:h.find_among_b(u,2)&&h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=o)if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,i=h.find_among_b(m,30)){switch(h.bra=h.cursor,h.limit_backward=e,i){case 1:if(!h.eq_s_b(1,"a"))return;break;case 2:case 9:if(!h.eq_s_b(1,"e"))return;break;case 3:if(!h.eq_s_b(1,"i"))return;break;case 4:if(!h.eq_s_b(1,"o"))return;break;case 5:if(!h.eq_s_b(1,"ä"))return;break;case 6:if(!h.eq_s_b(1,"ö"))return;break;case 7:if(r=h.limit-h.cursor,!j()&&(h.cursor=h.limit-r,!h.eq_s_b(2,"ie"))){h.cursor=h.limit-r;break}if(h.cursor=h.limit-r,h.cursor<=h.limit_backward){h.cursor=h.limit-r;break}h.cursor--,h.bra=h.cursor;break;case 8:if(!h.in_grouping_b(d,97,246)||!h.out_grouping_b(d,97,246))return}h.slice_del(),n=!0}else h.limit_backward=e}(),h.cursor=h.limit,function(){var i,e,r;if(h.cursor>=l)if(e=h.limit_backward,h.limit_backward=l,h.ket=h.cursor,i=h.find_among_b(w,14)){if(h.bra=h.cursor,h.limit_backward=e,1==i){if(r=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-r}h.slice_del()}else h.limit_backward=e}(),h.cursor=h.limit,h.cursor=(n?h.cursor>=o&&(i=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.find_among_b(_,2)?(h.bra=h.cursor,h.limit_backward=i,h.slice_del()):h.limit_backward=i):(h.cursor=h.limit,function(){var i,e,r,n,t,s;if(h.cursor>=o){if(e=h.limit_backward,h.limit_backward=o,h.ket=h.cursor,h.eq_s_b(1,"t")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.in_grouping_b(d,97,246)&&(h.cursor=h.limit-r,h.slice_del(),h.limit_backward=e,n=h.limit-h.cursor,h.cursor>=l&&(h.cursor=l,t=h.limit_backward,h.limit_backward=h.cursor,h.cursor=h.limit-n,h.ket=h.cursor,i=h.find_among_b(k,2))))){if(h.bra=h.cursor,h.limit_backward=t,1==i){if(s=h.limit-h.cursor,h.eq_s_b(2,"po"))return;h.cursor=h.limit-s}return h.slice_del()}h.limit_backward=e}}()),h.limit),function(){var i,e,r,n;if(h.cursor>=o){for(i=h.limit_backward,h.limit_backward=o,e=h.limit-h.cursor,j()&&(h.cursor=h.limit-e,h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.in_grouping_b(b,97,228)&&(h.bra=h.cursor,h.out_grouping_b(d,97,246)&&h.slice_del()),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"j")&&(h.bra=h.cursor,r=h.limit-h.cursor,h.eq_s_b(1,"o")?h.slice_del():(h.cursor=h.limit-r,h.eq_s_b(1,"u")&&h.slice_del())),h.cursor=h.limit-e,h.ket=h.cursor,h.eq_s_b(1,"o")&&(h.bra=h.cursor,h.eq_s_b(1,"j")&&h.slice_del()),h.cursor=h.limit-e,h.limit_backward=i;;){if(n=h.limit-h.cursor,h.out_grouping_b(d,97,246)){h.cursor=h.limit-n;break}if(h.cursor=h.limit-n,h.cursor<=h.limit_backward)return;h.cursor--}h.ket=h.cursor,h.cursor>h.limit_backward&&(h.cursor--,h.bra=h.cursor,t=h.slice_to(),h.eq_v_b(t)&&h.slice_del())}}(),!0}},function(i){return"function"==typeof i.update?i.update(function(i){return e.setCurrent(i),e.stem(),e.getCurrent()}):(e.setCurrent(i),e.stem(),e.getCurrent())}),i.Pipeline.registerFunction(i.fi.stemmer,"stemmer-fi"),i.fi.stopWordFilter=i.generateStopWordFilter("ei eivät emme en et ette että he heidän heidät heihin heille heillä heiltä heissä heistä heitä hän häneen hänelle hänellä häneltä hänen hänessä hänestä hänet häntä itse ja johon joiden joihin joiksi joilla joille joilta joina joissa joista joita joka joksi jolla jolle jolta jona jonka jos jossa josta jota jotka kanssa keiden keihin keiksi keille keillä keiltä keinä keissä keistä keitä keneen keneksi kenelle kenellä keneltä kenen kenenä kenessä kenestä kenet ketkä ketkä ketä koska kuin kuka kun me meidän meidät meihin meille meillä meiltä meissä meistä meitä mihin miksi mikä mille millä miltä minkä minkä minua minulla minulle minulta minun minussa minusta minut minuun minä minä missä mistä mitkä mitä mukaan mutta ne niiden niihin niiksi niille niillä niiltä niin niin niinä niissä niistä niitä noiden noihin noiksi noilla noille noilta noin noina noissa noista noita nuo nyt näiden näihin näiksi näille näillä näiltä näinä näissä näistä näitä nämä ole olemme olen olet olette oli olimme olin olisi olisimme olisin olisit olisitte olisivat olit olitte olivat olla olleet ollut on ovat poikki se sekä sen siihen siinä siitä siksi sille sillä sillä siltä sinua sinulla sinulle sinulta sinun sinussa sinusta sinut sinuun sinä sinä sitä tai te teidän teidät teihin teille teillä teiltä teissä teistä teitä tuo tuohon tuoksi tuolla tuolle tuolta tuon tuona tuossa tuosta tuota tähän täksi tälle tällä tältä tämä tämän tänä tässä tästä tätä vaan vai vaikka yli".split(" ")),i.Pipeline.registerFunction(i.fi.stopWordFilter,"stopWordFilter-fi")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.fr.js b/assets/javascripts/lunr/lunr.fr.js new file mode 100644 index 00000000..d043ec65 --- /dev/null +++ b/assets/javascripts/lunr/lunr.fr.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `French` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,y,s;e.fr=function(){this.pipeline.reset(),this.pipeline.add(e.fr.trimmer,e.fr.stopWordFilter,e.fr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.fr.stemmer))},e.fr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.fr.trimmer=e.trimmerSupport.generateTrimmer(e.fr.wordCharacters),e.Pipeline.registerFunction(e.fr.trimmer,"trimmer-fr"),e.fr.stemmer=(r=e.stemmerSupport.Among,y=e.stemmerSupport.SnowballProgram,s=new function(){var s,i,t,n=[new r("col",-1,-1),new r("par",-1,-1),new r("tap",-1,-1)],u=[new r("",-1,4),new r("I",0,1),new r("U",0,2),new r("Y",0,3)],o=[new r("iqU",-1,3),new r("abl",-1,3),new r("Ièr",-1,4),new r("ièr",-1,4),new r("eus",-1,2),new r("iv",-1,1)],c=[new r("ic",-1,2),new r("abil",-1,1),new r("iv",-1,3)],a=[new r("iqUe",-1,1),new r("atrice",-1,2),new r("ance",-1,1),new r("ence",-1,5),new r("logie",-1,3),new r("able",-1,1),new r("isme",-1,1),new r("euse",-1,11),new r("iste",-1,1),new r("ive",-1,8),new r("if",-1,8),new r("usion",-1,4),new r("ation",-1,2),new r("ution",-1,4),new r("ateur",-1,2),new r("iqUes",-1,1),new r("atrices",-1,2),new r("ances",-1,1),new r("ences",-1,5),new r("logies",-1,3),new r("ables",-1,1),new r("ismes",-1,1),new r("euses",-1,11),new r("istes",-1,1),new r("ives",-1,8),new r("ifs",-1,8),new r("usions",-1,4),new r("ations",-1,2),new r("utions",-1,4),new r("ateurs",-1,2),new r("ments",-1,15),new r("ements",30,6),new r("issements",31,12),new r("ités",-1,7),new r("ment",-1,15),new r("ement",34,6),new r("issement",35,12),new r("amment",34,13),new r("emment",34,14),new r("aux",-1,10),new r("eaux",39,9),new r("eux",-1,1),new r("ité",-1,7)],l=[new r("ira",-1,1),new r("ie",-1,1),new r("isse",-1,1),new r("issante",-1,1),new r("i",-1,1),new r("irai",4,1),new r("ir",-1,1),new r("iras",-1,1),new r("ies",-1,1),new r("îmes",-1,1),new r("isses",-1,1),new r("issantes",-1,1),new r("îtes",-1,1),new r("is",-1,1),new r("irais",13,1),new r("issais",13,1),new r("irions",-1,1),new r("issions",-1,1),new r("irons",-1,1),new r("issons",-1,1),new r("issants",-1,1),new r("it",-1,1),new r("irait",21,1),new r("issait",21,1),new r("issant",-1,1),new r("iraIent",-1,1),new r("issaIent",-1,1),new r("irent",-1,1),new r("issent",-1,1),new r("iront",-1,1),new r("ît",-1,1),new r("iriez",-1,1),new r("issiez",-1,1),new r("irez",-1,1),new r("issez",-1,1)],w=[new r("a",-1,3),new r("era",0,2),new r("asse",-1,3),new r("ante",-1,3),new r("ée",-1,2),new r("ai",-1,3),new r("erai",5,2),new r("er",-1,2),new r("as",-1,3),new r("eras",8,2),new r("âmes",-1,3),new r("asses",-1,3),new r("antes",-1,3),new r("âtes",-1,3),new r("ées",-1,2),new r("ais",-1,3),new r("erais",15,2),new r("ions",-1,1),new r("erions",17,2),new r("assions",17,3),new r("erons",-1,2),new r("ants",-1,3),new r("és",-1,2),new r("ait",-1,3),new r("erait",23,2),new r("ant",-1,3),new r("aIent",-1,3),new r("eraIent",26,2),new r("èrent",-1,2),new r("assent",-1,3),new r("eront",-1,2),new r("ât",-1,3),new r("ez",-1,2),new r("iez",32,2),new r("eriez",33,2),new r("assiez",33,3),new r("erez",32,2),new r("é",-1,2)],f=[new r("e",-1,3),new r("Ière",0,2),new r("ière",0,2),new r("ion",-1,1),new r("Ier",-1,2),new r("ier",-1,2),new r("ë",-1,4)],m=[new r("ell",-1,-1),new r("eill",-1,-1),new r("enn",-1,-1),new r("onn",-1,-1),new r("ett",-1,-1)],_=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,128,130,103,8,5],b=[1,65,20,0,0,0,0,0,0,0,0,0,0,0,0,0,128],d=new y;function k(e,r,s){return!(!d.eq_s(1,e)||(d.ket=d.cursor,!d.in_grouping(_,97,251)))&&(d.slice_from(r),d.cursor=s,!0)}function p(e,r,s){return!!d.eq_s(1,e)&&(d.ket=d.cursor,d.slice_from(r),d.cursor=s,!0)}function g(){for(;!d.in_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}for(;!d.out_grouping(_,97,251);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}function q(){return t<=d.cursor}function v(){return i<=d.cursor}function h(){return s<=d.cursor}function z(){if(!function(){var e,r;if(d.ket=d.cursor,e=d.find_among_b(a,43)){switch(d.bra=d.cursor,e){case 1:if(!h())return!1;d.slice_del();break;case 2:if(!h())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")&&(d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU"));break;case 3:if(!h())return!1;d.slice_from("log");break;case 4:if(!h())return!1;d.slice_from("u");break;case 5:if(!h())return!1;d.slice_from("ent");break;case 6:if(!q())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(o,6))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&d.slice_del()));break;case 2:h()?d.slice_del():v()&&d.slice_from("eux");break;case 3:h()&&d.slice_del();break;case 4:q()&&d.slice_from("i")}break;case 7:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,e=d.find_among_b(c,3))switch(d.bra=d.cursor,e){case 1:h()?d.slice_del():d.slice_from("abl");break;case 2:h()?d.slice_del():d.slice_from("iqU");break;case 3:h()&&d.slice_del()}break;case 8:if(!h())return!1;if(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,h()&&(d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"ic")))){d.bra=d.cursor,h()?d.slice_del():d.slice_from("iqU");break}break;case 9:d.slice_from("eau");break;case 10:if(!v())return!1;d.slice_from("al");break;case 11:if(h())d.slice_del();else{if(!v())return!1;d.slice_from("eux")}break;case 12:if(!v()||!d.out_grouping_b(_,97,251))return!1;d.slice_del();break;case 13:return q()&&d.slice_from("ant"),!1;case 14:return q()&&d.slice_from("ent"),!1;case 15:return r=d.limit-d.cursor,d.in_grouping_b(_,97,251)&&q()&&(d.cursor=d.limit-r,d.slice_del()),!1}return!0}return!1}()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor=t){if(s=d.limit_backward,d.limit_backward=t,d.ket=d.cursor,e=d.find_among_b(f,7))switch(d.bra=d.cursor,e){case 1:if(h()){if(i=d.limit-d.cursor,!d.eq_s_b(1,"s")&&(d.cursor=d.limit-i,!d.eq_s_b(1,"t")))break;d.slice_del()}break;case 2:d.slice_from("i");break;case 3:d.slice_del();break;case 4:d.eq_s_b(2,"gu")&&d.slice_del()}d.limit_backward=s}}();d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"Y")?(d.bra=d.cursor,d.slice_from("i")):(d.cursor=d.limit,d.eq_s_b(1,"ç")&&(d.bra=d.cursor,d.slice_from("c")))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e,r;;){if(e=d.cursor,d.in_grouping(_,97,251)){if(d.bra=d.cursor,r=d.cursor,k("u","U",e))continue;if(d.cursor=r,k("i","I",e))continue;if(d.cursor=r,p("y","Y",e))continue}if(d.cursor=e,!k("y","Y",d.bra=e)){if(d.cursor=e,d.eq_s(1,"q")&&(d.bra=d.cursor,p("u","U",e)))continue;if((d.cursor=e)>=d.limit)return;d.cursor++}}}(),d.cursor=r,function(){var e=d.cursor;if(t=d.limit,s=i=t,d.in_grouping(_,97,251)&&d.in_grouping(_,97,251)&&d.cursor=d.limit){d.cursor=t;break}d.cursor++}while(!d.in_grouping(_,97,251))}t=d.cursor,d.cursor=e,g()||(i=d.cursor,g()||(s=d.cursor))}(),d.limit_backward=r,d.cursor=d.limit,z(),d.cursor=d.limit,e=d.limit-d.cursor,d.find_among_b(m,5)&&(d.cursor=d.limit-e,d.ket=d.cursor,d.cursor>d.limit_backward&&(d.cursor--,d.bra=d.cursor,d.slice_del())),d.cursor=d.limit,function(){for(var e,r=1;d.out_grouping_b(_,97,251);)r--;if(r<=0){if(d.ket=d.cursor,e=d.limit-d.cursor,!d.eq_s_b(1,"é")&&(d.cursor=d.limit-e,!d.eq_s_b(1,"è")))return;d.bra=d.cursor,d.slice_from("e")}}(),d.cursor=d.limit_backward,function(){for(var e,r;r=d.cursor,d.bra=r,e=d.find_among(u,4);)switch(d.ket=d.cursor,e){case 1:d.slice_from("i");break;case 2:d.slice_from("u");break;case 3:d.slice_from("y");break;case 4:if(d.cursor>=d.limit)return;d.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return s.setCurrent(e),s.stem(),s.getCurrent()}):(s.setCurrent(e),s.stem(),s.getCurrent())}),e.Pipeline.registerFunction(e.fr.stemmer,"stemmer-fr"),e.fr.stopWordFilter=e.generateStopWordFilter("ai aie aient aies ait as au aura aurai auraient aurais aurait auras aurez auriez aurions aurons auront aux avaient avais avait avec avez aviez avions avons ayant ayez ayons c ce ceci celà ces cet cette d dans de des du elle en es est et eu eue eues eurent eus eusse eussent eusses eussiez eussions eut eux eûmes eût eûtes furent fus fusse fussent fusses fussiez fussions fut fûmes fût fûtes ici il ils j je l la le les leur leurs lui m ma mais me mes moi mon même n ne nos notre nous on ont ou par pas pour qu que quel quelle quelles quels qui s sa sans se sera serai seraient serais serait seras serez seriez serions serons seront ses soi soient sois soit sommes son sont soyez soyons suis sur t ta te tes toi ton tu un une vos votre vous y à étaient étais était étant étiez étions été étée étées étés êtes".split(" ")),e.Pipeline.registerFunction(e.fr.stopWordFilter,"stopWordFilter-fr")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.hu.js b/assets/javascripts/lunr/lunr.hu.js new file mode 100644 index 00000000..bfc68db8 --- /dev/null +++ b/assets/javascripts/lunr/lunr.hu.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Hungarian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var p,_,n;e.hu=function(){this.pipeline.reset(),this.pipeline.add(e.hu.trimmer,e.hu.stopWordFilter,e.hu.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hu.stemmer))},e.hu.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.hu.trimmer=e.trimmerSupport.generateTrimmer(e.hu.wordCharacters),e.Pipeline.registerFunction(e.hu.trimmer,"trimmer-hu"),e.hu.stemmer=(p=e.stemmerSupport.Among,_=e.stemmerSupport.SnowballProgram,n=new function(){var r,i=[new p("cs",-1,-1),new p("dzs",-1,-1),new p("gy",-1,-1),new p("ly",-1,-1),new p("ny",-1,-1),new p("sz",-1,-1),new p("ty",-1,-1),new p("zs",-1,-1)],n=[new p("á",-1,1),new p("é",-1,2)],a=[new p("bb",-1,-1),new p("cc",-1,-1),new p("dd",-1,-1),new p("ff",-1,-1),new p("gg",-1,-1),new p("jj",-1,-1),new p("kk",-1,-1),new p("ll",-1,-1),new p("mm",-1,-1),new p("nn",-1,-1),new p("pp",-1,-1),new p("rr",-1,-1),new p("ccs",-1,-1),new p("ss",-1,-1),new p("zzs",-1,-1),new p("tt",-1,-1),new p("vv",-1,-1),new p("ggy",-1,-1),new p("lly",-1,-1),new p("nny",-1,-1),new p("tty",-1,-1),new p("ssz",-1,-1),new p("zz",-1,-1)],t=[new p("al",-1,1),new p("el",-1,2)],e=[new p("ba",-1,-1),new p("ra",-1,-1),new p("be",-1,-1),new p("re",-1,-1),new p("ig",-1,-1),new p("nak",-1,-1),new p("nek",-1,-1),new p("val",-1,-1),new p("vel",-1,-1),new p("ul",-1,-1),new p("nál",-1,-1),new p("nél",-1,-1),new p("ból",-1,-1),new p("ról",-1,-1),new p("tól",-1,-1),new p("bõl",-1,-1),new p("rõl",-1,-1),new p("tõl",-1,-1),new p("ül",-1,-1),new p("n",-1,-1),new p("an",19,-1),new p("ban",20,-1),new p("en",19,-1),new p("ben",22,-1),new p("képpen",22,-1),new p("on",19,-1),new p("ön",19,-1),new p("képp",-1,-1),new p("kor",-1,-1),new p("t",-1,-1),new p("at",29,-1),new p("et",29,-1),new p("ként",29,-1),new p("anként",32,-1),new p("enként",32,-1),new p("onként",32,-1),new p("ot",29,-1),new p("ért",29,-1),new p("öt",29,-1),new p("hez",-1,-1),new p("hoz",-1,-1),new p("höz",-1,-1),new p("vá",-1,-1),new p("vé",-1,-1)],s=[new p("án",-1,2),new p("én",-1,1),new p("ánként",-1,3)],c=[new p("stul",-1,2),new p("astul",0,1),new p("ástul",0,3),new p("stül",-1,2),new p("estül",3,1),new p("éstül",3,4)],w=[new p("á",-1,1),new p("é",-1,2)],o=[new p("k",-1,7),new p("ak",0,4),new p("ek",0,6),new p("ok",0,5),new p("ák",0,1),new p("ék",0,2),new p("ök",0,3)],l=[new p("éi",-1,7),new p("áéi",0,6),new p("ééi",0,5),new p("é",-1,9),new p("ké",3,4),new p("aké",4,1),new p("eké",4,1),new p("oké",4,1),new p("áké",4,3),new p("éké",4,2),new p("öké",4,1),new p("éé",3,8)],u=[new p("a",-1,18),new p("ja",0,17),new p("d",-1,16),new p("ad",2,13),new p("ed",2,13),new p("od",2,13),new p("ád",2,14),new p("éd",2,15),new p("öd",2,13),new p("e",-1,18),new p("je",9,17),new p("nk",-1,4),new p("unk",11,1),new p("ánk",11,2),new p("énk",11,3),new p("ünk",11,1),new p("uk",-1,8),new p("juk",16,7),new p("ájuk",17,5),new p("ük",-1,8),new p("jük",19,7),new p("éjük",20,6),new p("m",-1,12),new p("am",22,9),new p("em",22,9),new p("om",22,9),new p("ám",22,10),new p("ém",22,11),new p("o",-1,18),new p("á",-1,19),new p("é",-1,20)],m=[new p("id",-1,10),new p("aid",0,9),new p("jaid",1,6),new p("eid",0,9),new p("jeid",3,6),new p("áid",0,7),new p("éid",0,8),new p("i",-1,15),new p("ai",7,14),new p("jai",8,11),new p("ei",7,14),new p("jei",10,11),new p("ái",7,12),new p("éi",7,13),new p("itek",-1,24),new p("eitek",14,21),new p("jeitek",15,20),new p("éitek",14,23),new p("ik",-1,29),new p("aik",18,26),new p("jaik",19,25),new p("eik",18,26),new p("jeik",21,25),new p("áik",18,27),new p("éik",18,28),new p("ink",-1,20),new p("aink",25,17),new p("jaink",26,16),new p("eink",25,17),new p("jeink",28,16),new p("áink",25,18),new p("éink",25,19),new p("aitok",-1,21),new p("jaitok",32,20),new p("áitok",-1,22),new p("im",-1,5),new p("aim",35,4),new p("jaim",36,1),new p("eim",35,4),new p("jeim",38,1),new p("áim",35,2),new p("éim",35,3)],k=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,1,17,52,14],f=new _;function b(){return r<=f.cursor}function d(){var e=f.limit-f.cursor;return!!f.find_among_b(a,23)&&(f.cursor=f.limit-e,!0)}function g(){if(f.cursor>f.limit_backward){f.cursor--,f.ket=f.cursor;var e=f.cursor-1;f.limit_backward<=e&&e<=f.limit&&(f.cursor=e,f.bra=e,f.slice_del())}}function h(){f.ket=f.cursor,f.find_among_b(e,44)&&(f.bra=f.cursor,b()&&(f.slice_del(),function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(n,2))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e")}}()))}this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var e=f.cursor;return function(){var e,n=f.cursor;if(r=f.limit,f.in_grouping(k,97,252))for(;;){if(e=f.cursor,f.out_grouping(k,97,252))return f.cursor=e,f.find_among(i,8)||(f.cursor=e)=f.limit)return r=e;f.cursor++}if(f.cursor=n,f.out_grouping(k,97,252)){for(;!f.in_grouping(k,97,252);){if(f.cursor>=f.limit)return;f.cursor++}r=f.cursor}}(),f.limit_backward=e,f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(t,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,h(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(s,3))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("e");break;case 2:case 3:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(c,6))&&(f.bra=f.cursor,b()))switch(e){case 1:case 2:f.slice_del();break;case 3:f.slice_from("a");break;case 4:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(w,2))&&(f.bra=f.cursor,b())){if((1==e||2==e)&&!d())return;f.slice_del(),g()}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(l,12))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 9:f.slice_del();break;case 2:case 5:case 8:f.slice_from("e");break;case 3:case 6:f.slice_from("a")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(u,31))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 7:case 8:case 9:case 12:case 13:case 16:case 17:case 18:f.slice_del();break;case 2:case 5:case 10:case 14:case 19:f.slice_from("a");break;case 3:case 6:case 11:case 15:case 20:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(m,42))&&(f.bra=f.cursor,b()))switch(e){case 1:case 4:case 5:case 6:case 9:case 10:case 11:case 14:case 15:case 16:case 17:case 20:case 21:case 24:case 25:case 26:case 29:f.slice_del();break;case 2:case 7:case 12:case 18:case 22:case 27:f.slice_from("a");break;case 3:case 8:case 13:case 19:case 23:case 28:f.slice_from("e")}}(),f.cursor=f.limit,function(){var e;if(f.ket=f.cursor,(e=f.find_among_b(o,7))&&(f.bra=f.cursor,b()))switch(e){case 1:f.slice_from("a");break;case 2:f.slice_from("e");break;case 3:case 4:case 5:case 6:case 7:f.slice_del()}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.hu.stemmer,"stemmer-hu"),e.hu.stopWordFilter=e.generateStopWordFilter("a abban ahhoz ahogy ahol aki akik akkor alatt amely amelyek amelyekben amelyeket amelyet amelynek ami amikor amit amolyan amíg annak arra arról az azok azon azonban azt aztán azután azzal azért be belül benne bár cikk cikkek cikkeket csak de e ebben eddig egy egyes egyetlen egyik egyre egyéb egész ehhez ekkor el ellen elsõ elég elõ elõször elõtt emilyen ennek erre ez ezek ezen ezt ezzel ezért fel felé hanem hiszen hogy hogyan igen ill ill. illetve ilyen ilyenkor ismét ison itt jobban jó jól kell kellett keressünk keresztül ki kívül között közül legalább legyen lehet lehetett lenne lenni lesz lett maga magát majd majd meg mellett mely melyek mert mi mikor milyen minden mindenki mindent mindig mint mintha mit mivel miért most már más másik még míg nagy nagyobb nagyon ne nekem neki nem nincs néha néhány nélkül olyan ott pedig persze rá s saját sem semmi sok sokat sokkal szemben szerint szinte számára talán tehát teljes tovább továbbá több ugyanis utolsó után utána vagy vagyis vagyok valaki valami valamint való van vannak vele vissza viszont volna volt voltak voltam voltunk által általában át én éppen és így õ õk õket össze úgy új újabb újra".split(" ")),e.Pipeline.registerFunction(e.hu.stopWordFilter,"stopWordFilter-hu")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.it.js b/assets/javascripts/lunr/lunr.it.js new file mode 100644 index 00000000..58a46fb6 --- /dev/null +++ b/assets/javascripts/lunr/lunr.it.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Italian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var z,P,r;e.it=function(){this.pipeline.reset(),this.pipeline.add(e.it.trimmer,e.it.stopWordFilter,e.it.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.it.stemmer))},e.it.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.it.trimmer=e.trimmerSupport.generateTrimmer(e.it.wordCharacters),e.Pipeline.registerFunction(e.it.trimmer,"trimmer-it"),e.it.stemmer=(z=e.stemmerSupport.Among,P=e.stemmerSupport.SnowballProgram,r=new function(){var o,t,s,a=[new z("",-1,7),new z("qu",0,6),new z("á",0,1),new z("é",0,2),new z("í",0,3),new z("ó",0,4),new z("ú",0,5)],u=[new z("",-1,3),new z("I",0,1),new z("U",0,2)],c=[new z("la",-1,-1),new z("cela",0,-1),new z("gliela",0,-1),new z("mela",0,-1),new z("tela",0,-1),new z("vela",0,-1),new z("le",-1,-1),new z("cele",6,-1),new z("gliele",6,-1),new z("mele",6,-1),new z("tele",6,-1),new z("vele",6,-1),new z("ne",-1,-1),new z("cene",12,-1),new z("gliene",12,-1),new z("mene",12,-1),new z("sene",12,-1),new z("tene",12,-1),new z("vene",12,-1),new z("ci",-1,-1),new z("li",-1,-1),new z("celi",20,-1),new z("glieli",20,-1),new z("meli",20,-1),new z("teli",20,-1),new z("veli",20,-1),new z("gli",20,-1),new z("mi",-1,-1),new z("si",-1,-1),new z("ti",-1,-1),new z("vi",-1,-1),new z("lo",-1,-1),new z("celo",31,-1),new z("glielo",31,-1),new z("melo",31,-1),new z("telo",31,-1),new z("velo",31,-1)],w=[new z("ando",-1,1),new z("endo",-1,1),new z("ar",-1,2),new z("er",-1,2),new z("ir",-1,2)],r=[new z("ic",-1,-1),new z("abil",-1,-1),new z("os",-1,-1),new z("iv",-1,1)],n=[new z("ic",-1,1),new z("abil",-1,1),new z("iv",-1,1)],i=[new z("ica",-1,1),new z("logia",-1,3),new z("osa",-1,1),new z("ista",-1,1),new z("iva",-1,9),new z("anza",-1,1),new z("enza",-1,5),new z("ice",-1,1),new z("atrice",7,1),new z("iche",-1,1),new z("logie",-1,3),new z("abile",-1,1),new z("ibile",-1,1),new z("usione",-1,4),new z("azione",-1,2),new z("uzione",-1,4),new z("atore",-1,2),new z("ose",-1,1),new z("ante",-1,1),new z("mente",-1,1),new z("amente",19,7),new z("iste",-1,1),new z("ive",-1,9),new z("anze",-1,1),new z("enze",-1,5),new z("ici",-1,1),new z("atrici",25,1),new z("ichi",-1,1),new z("abili",-1,1),new z("ibili",-1,1),new z("ismi",-1,1),new z("usioni",-1,4),new z("azioni",-1,2),new z("uzioni",-1,4),new z("atori",-1,2),new z("osi",-1,1),new z("anti",-1,1),new z("amenti",-1,6),new z("imenti",-1,6),new z("isti",-1,1),new z("ivi",-1,9),new z("ico",-1,1),new z("ismo",-1,1),new z("oso",-1,1),new z("amento",-1,6),new z("imento",-1,6),new z("ivo",-1,9),new z("ità",-1,8),new z("istà",-1,1),new z("istè",-1,1),new z("istì",-1,1)],l=[new z("isca",-1,1),new z("enda",-1,1),new z("ata",-1,1),new z("ita",-1,1),new z("uta",-1,1),new z("ava",-1,1),new z("eva",-1,1),new z("iva",-1,1),new z("erebbe",-1,1),new z("irebbe",-1,1),new z("isce",-1,1),new z("ende",-1,1),new z("are",-1,1),new z("ere",-1,1),new z("ire",-1,1),new z("asse",-1,1),new z("ate",-1,1),new z("avate",16,1),new z("evate",16,1),new z("ivate",16,1),new z("ete",-1,1),new z("erete",20,1),new z("irete",20,1),new z("ite",-1,1),new z("ereste",-1,1),new z("ireste",-1,1),new z("ute",-1,1),new z("erai",-1,1),new z("irai",-1,1),new z("isci",-1,1),new z("endi",-1,1),new z("erei",-1,1),new z("irei",-1,1),new z("assi",-1,1),new z("ati",-1,1),new z("iti",-1,1),new z("eresti",-1,1),new z("iresti",-1,1),new z("uti",-1,1),new z("avi",-1,1),new z("evi",-1,1),new z("ivi",-1,1),new z("isco",-1,1),new z("ando",-1,1),new z("endo",-1,1),new z("Yamo",-1,1),new z("iamo",-1,1),new z("avamo",-1,1),new z("evamo",-1,1),new z("ivamo",-1,1),new z("eremo",-1,1),new z("iremo",-1,1),new z("assimo",-1,1),new z("ammo",-1,1),new z("emmo",-1,1),new z("eremmo",54,1),new z("iremmo",54,1),new z("immo",-1,1),new z("ano",-1,1),new z("iscano",58,1),new z("avano",58,1),new z("evano",58,1),new z("ivano",58,1),new z("eranno",-1,1),new z("iranno",-1,1),new z("ono",-1,1),new z("iscono",65,1),new z("arono",65,1),new z("erono",65,1),new z("irono",65,1),new z("erebbero",-1,1),new z("irebbero",-1,1),new z("assero",-1,1),new z("essero",-1,1),new z("issero",-1,1),new z("ato",-1,1),new z("ito",-1,1),new z("uto",-1,1),new z("avo",-1,1),new z("evo",-1,1),new z("ivo",-1,1),new z("ar",-1,1),new z("ir",-1,1),new z("erà",-1,1),new z("irà",-1,1),new z("erò",-1,1),new z("irò",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2,1],f=[17,65,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,8,2],v=[17],b=new P;function d(e,r,n){return!(!b.eq_s(1,e)||(b.ket=b.cursor,!b.in_grouping(m,97,249)))&&(b.slice_from(r),b.cursor=n,!0)}function _(e){if(b.cursor=e,!b.in_grouping(m,97,249))return!1;for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function g(){var e,r=b.cursor;if(!function(){if(b.in_grouping(m,97,249)){var e=b.cursor;if(b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return _(e);b.cursor++}return!0}return _(e)}return!1}()){if(b.cursor=r,!b.out_grouping(m,97,249))return;if(e=b.cursor,b.out_grouping(m,97,249)){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return b.cursor=e,void(b.in_grouping(m,97,249)&&b.cursor=b.limit)return;b.cursor++}s=b.cursor}function p(){for(;!b.in_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}for(;!b.out_grouping(m,97,249);){if(b.cursor>=b.limit)return!1;b.cursor++}return!0}function k(){return s<=b.cursor}function h(){return o<=b.cursor}function q(){var e;if(b.ket=b.cursor,!(e=b.find_among_b(i,51)))return!1;switch(b.bra=b.cursor,e){case 1:if(!h())return!1;b.slice_del();break;case 2:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del());break;case 3:if(!h())return!1;b.slice_from("log");break;case 4:if(!h())return!1;b.slice_from("u");break;case 5:if(!h())return!1;b.slice_from("ente");break;case 6:if(!k())return!1;b.slice_del();break;case 7:if(!(t<=b.cursor))return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(r,4))&&(b.bra=b.cursor,h()&&(b.slice_del(),1==e&&(b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&b.slice_del()))));break;case 8:if(!h())return!1;b.slice_del(),b.ket=b.cursor,(e=b.find_among_b(n,3))&&(b.bra=b.cursor,1==e&&h()&&b.slice_del());break;case 9:if(!h())return!1;b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"at")&&(b.bra=b.cursor,h()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(2,"ic")&&(b.bra=b.cursor,h()&&b.slice_del())))}return!0}function C(){var e;e=b.limit-b.cursor,b.ket=b.cursor,b.in_grouping_b(f,97,242)&&(b.bra=b.cursor,k()&&(b.slice_del(),b.ket=b.cursor,b.eq_s_b(1,"i")&&(b.bra=b.cursor,k())))?b.slice_del():b.cursor=b.limit-e,b.ket=b.cursor,b.eq_s_b(1,"h")&&(b.bra=b.cursor,b.in_grouping_b(v,99,103)&&k()&&b.slice_del())}this.setCurrent=function(e){b.setCurrent(e)},this.getCurrent=function(){return b.getCurrent()},this.stem=function(){var e,r,n,i=b.cursor;return function(){for(var e,r,n,i,o=b.cursor;;){if(b.bra=b.cursor,e=b.find_among(a,7))switch(b.ket=b.cursor,e){case 1:b.slice_from("à");continue;case 2:b.slice_from("è");continue;case 3:b.slice_from("ì");continue;case 4:b.slice_from("ò");continue;case 5:b.slice_from("ù");continue;case 6:b.slice_from("qU");continue;case 7:if(b.cursor>=b.limit)break;b.cursor++;continue}break}for(b.cursor=o;;)for(r=b.cursor;;){if(n=b.cursor,b.in_grouping(m,97,249)){if(b.bra=b.cursor,i=b.cursor,d("u","U",n))break;if(b.cursor=i,d("i","I",n))break}if(b.cursor=n,b.cursor>=b.limit)return b.cursor=r;b.cursor++}}(),b.cursor=i,e=b.cursor,s=b.limit,o=t=s,g(),b.cursor=e,p()&&(t=b.cursor,p()&&(o=b.cursor)),b.limit_backward=i,b.cursor=b.limit,function(){var e;if(b.ket=b.cursor,b.find_among_b(c,37)&&(b.bra=b.cursor,(e=b.find_among_b(w,5))&&k()))switch(e){case 1:b.slice_del();break;case 2:b.slice_from("e")}}(),b.cursor=b.limit,q()||(b.cursor=b.limit,b.cursor>=s&&(n=b.limit_backward,b.limit_backward=s,b.ket=b.cursor,(r=b.find_among_b(l,87))&&(b.bra=b.cursor,1==r&&b.slice_del()),b.limit_backward=n)),b.cursor=b.limit,C(),b.cursor=b.limit_backward,function(){for(var e;b.bra=b.cursor,e=b.find_among(u,3);)switch(b.ket=b.cursor,e){case 1:b.slice_from("i");break;case 2:b.slice_from("u");break;case 3:if(b.cursor>=b.limit)return;b.cursor++}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.it.stemmer,"stemmer-it"),e.it.stopWordFilter=e.generateStopWordFilter("a abbia abbiamo abbiano abbiate ad agl agli ai al all alla alle allo anche avemmo avendo avesse avessero avessi avessimo aveste avesti avete aveva avevamo avevano avevate avevi avevo avrai avranno avrebbe avrebbero avrei avremmo avremo avreste avresti avrete avrà avrò avuta avute avuti avuto c che chi ci coi col come con contro cui da dagl dagli dai dal dall dalla dalle dallo degl degli dei del dell della delle dello di dov dove e ebbe ebbero ebbi ed era erano eravamo eravate eri ero essendo faccia facciamo facciano facciate faccio facemmo facendo facesse facessero facessi facessimo faceste facesti faceva facevamo facevano facevate facevi facevo fai fanno farai faranno farebbe farebbero farei faremmo faremo fareste faresti farete farà farò fece fecero feci fosse fossero fossi fossimo foste fosti fu fui fummo furono gli ha hai hanno ho i il in io l la le lei li lo loro lui ma mi mia mie miei mio ne negl negli nei nel nell nella nelle nello noi non nostra nostre nostri nostro o per perché più quale quanta quante quanti quanto quella quelle quelli quello questa queste questi questo sarai saranno sarebbe sarebbero sarei saremmo saremo sareste saresti sarete sarà sarò se sei si sia siamo siano siate siete sono sta stai stando stanno starai staranno starebbe starebbero starei staremmo staremo stareste staresti starete starà starò stava stavamo stavano stavate stavi stavo stemmo stesse stessero stessi stessimo steste stesti stette stettero stetti stia stiamo stiano stiate sto su sua sue sugl sugli sui sul sull sulla sulle sullo suo suoi ti tra tu tua tue tuo tuoi tutti tutto un una uno vi voi vostra vostre vostri vostro è".split(" ")),e.Pipeline.registerFunction(e.it.stopWordFilter,"stopWordFilter-it")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.ja.js b/assets/javascripts/lunr/lunr.ja.js new file mode 100644 index 00000000..715b834a --- /dev/null +++ b/assets/javascripts/lunr/lunr.ja.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Japanese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Chad Liu + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(m){if(void 0===m)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===m.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var l="2"==m.version[0];m.ja=function(){this.pipeline.reset(),this.pipeline.add(m.ja.trimmer,m.ja.stopWordFilter,m.ja.stemmer),l?this.tokenizer=m.ja.tokenizer:(m.tokenizer&&(m.tokenizer=m.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=m.ja.tokenizer))};var j=new m.TinySegmenter;m.ja.tokenizer=function(e){var r,t,i,n,o,s,p,a,u;if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return l?new m.Token(e.toLowerCase()):e.toLowerCase()});for(r=(t=e.toString().toLowerCase().replace(/^\s+/,"")).length-1;0<=r;r--)if(/\S/.test(t.charAt(r))){t=t.substring(0,r+1);break}for(o=[],i=t.length,p=a=0;a<=i;a++)if(s=a-p,t.charAt(a).match(/\s/)||a==i){if(0=_.limit||(_.cursor++,!1)}function w(){for(;!_.in_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}for(;!_.out_grouping(m,97,232);){if(_.cursor>=_.limit)return!0;_.cursor++}return!1}function b(){return i<=_.cursor}function p(){return e<=_.cursor}function g(){var r=_.limit-_.cursor;_.find_among_b(t,3)&&(_.cursor=_.limit-r,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del()))}function h(){var r;u=!1,_.ket=_.cursor,_.eq_s_b(1,"e")&&(_.bra=_.cursor,b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.slice_del(),u=!0,g())))}function k(){var r;b()&&(r=_.limit-_.cursor,_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-r,_.eq_s_b(3,"gem")||(_.cursor=_.limit-r,_.slice_del(),g())))}this.setCurrent=function(r){_.setCurrent(r)},this.getCurrent=function(){return _.getCurrent()},this.stem=function(){var r=_.cursor;return function(){for(var r,e,i,n=_.cursor;;){if(_.bra=_.cursor,r=_.find_among(o,11))switch(_.ket=_.cursor,r){case 1:_.slice_from("a");continue;case 2:_.slice_from("e");continue;case 3:_.slice_from("i");continue;case 4:_.slice_from("o");continue;case 5:_.slice_from("u");continue;case 6:if(_.cursor>=_.limit)break;_.cursor++;continue}break}for(_.cursor=n,_.bra=n,_.eq_s(1,"y")?(_.ket=_.cursor,_.slice_from("Y")):_.cursor=n;;)if(e=_.cursor,_.in_grouping(m,97,232)){if(i=_.cursor,_.bra=i,_.eq_s(1,"i"))_.ket=_.cursor,_.in_grouping(m,97,232)&&(_.slice_from("I"),_.cursor=e);else if(_.cursor=i,_.eq_s(1,"y"))_.ket=_.cursor,_.slice_from("Y"),_.cursor=e;else if(s(e))break}else if(s(e))break}(),_.cursor=r,i=_.limit,e=i,w()||((i=_.cursor)<3&&(i=3),w()||(e=_.cursor)),_.limit_backward=r,_.cursor=_.limit,function(){var r,e,i,n,o,t,s=_.limit-_.cursor;if(_.ket=_.cursor,r=_.find_among_b(c,5))switch(_.bra=_.cursor,r){case 1:b()&&_.slice_from("heid");break;case 2:k();break;case 3:b()&&_.out_grouping_b(f,97,232)&&_.slice_del()}if(_.cursor=_.limit-s,h(),_.cursor=_.limit-s,_.ket=_.cursor,_.eq_s_b(4,"heid")&&(_.bra=_.cursor,p()&&(e=_.limit-_.cursor,_.eq_s_b(1,"c")||(_.cursor=_.limit-e,_.slice_del(),_.ket=_.cursor,_.eq_s_b(2,"en")&&(_.bra=_.cursor,k())))),_.cursor=_.limit-s,_.ket=_.cursor,r=_.find_among_b(a,6))switch(_.bra=_.cursor,r){case 1:if(p()){if(_.slice_del(),i=_.limit-_.cursor,_.ket=_.cursor,_.eq_s_b(2,"ig")&&(_.bra=_.cursor,p()&&(n=_.limit-_.cursor,!_.eq_s_b(1,"e")))){_.cursor=_.limit-n,_.slice_del();break}_.cursor=_.limit-i,g()}break;case 2:p()&&(o=_.limit-_.cursor,_.eq_s_b(1,"e")||(_.cursor=_.limit-o,_.slice_del()));break;case 3:p()&&(_.slice_del(),h());break;case 4:p()&&_.slice_del();break;case 5:p()&&u&&_.slice_del()}_.cursor=_.limit-s,_.out_grouping_b(d,73,232)&&(t=_.limit-_.cursor,_.find_among_b(l,4)&&_.out_grouping_b(m,97,232)&&(_.cursor=_.limit-t,_.ket=_.cursor,_.cursor>_.limit_backward&&(_.cursor--,_.bra=_.cursor,_.slice_del())))}(),_.cursor=_.limit_backward,function(){for(var r;;)if(_.bra=_.cursor,r=_.find_among(n,3))switch(_.ket=_.cursor,r){case 1:_.slice_from("y");break;case 2:_.slice_from("i");break;case 3:if(_.cursor>=_.limit)return;_.cursor++}}(),!0}},function(r){return"function"==typeof r.update?r.update(function(r){return e.setCurrent(r),e.stem(),e.getCurrent()}):(e.setCurrent(r),e.stem(),e.getCurrent())}),r.Pipeline.registerFunction(r.nl.stemmer,"stemmer-nl"),r.nl.stopWordFilter=r.generateStopWordFilter(" aan al alles als altijd andere ben bij daar dan dat de der deze die dit doch doen door dus een eens en er ge geen geweest haar had heb hebben heeft hem het hier hij hoe hun iemand iets ik in is ja je kan kon kunnen maar me meer men met mij mijn moet na naar niet niets nog nu of om omdat onder ons ook op over reeds te tegen toch toen tot u uit uw van veel voor want waren was wat werd wezen wie wil worden wordt zal ze zelf zich zij zijn zo zonder zou".split(" ")),r.Pipeline.registerFunction(r.nl.stopWordFilter,"stopWordFilter-nl")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.no.js b/assets/javascripts/lunr/lunr.no.js new file mode 100644 index 00000000..031e4b20 --- /dev/null +++ b/assets/javascripts/lunr/lunr.no.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Norwegian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var o,s,a=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],m=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],u=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],d=[119,125,149,1],c=new n;this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var e,r,n,i,t=c.cursor;return function(){var e,r=c.cursor+3;if(s=c.limit,0<=r||r<=c.limit){for(o=r;;){if(e=c.cursor,c.in_grouping(u,97,248)){c.cursor=e;break}if(e>=c.limit)return;c.cursor=e+1}for(;!c.out_grouping(u,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(s=c.cursor)=s&&(r=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,e=c.find_among_b(a,29),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:n=c.limit-c.cursor,c.in_grouping_b(d,98,122)?c.slice_del():(c.cursor=c.limit-n,c.eq_s_b(1,"k")&&c.out_grouping_b(u,97,248)&&c.slice_del());break;case 3:c.slice_from("er")}}(),c.cursor=c.limit,r=c.limit-c.cursor,c.cursor>=s&&(e=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,c.find_among_b(m,2)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e),c.cursor=c.limit,c.cursor>=s&&(i=c.limit_backward,c.limit_backward=s,c.ket=c.cursor,(n=c.find_among_b(l,11))?(c.bra=c.cursor,c.limit_backward=i,1==n&&c.slice_del()):c.limit_backward=i),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.pt.js b/assets/javascripts/lunr/lunr.pt.js new file mode 100644 index 00000000..59e766fe --- /dev/null +++ b/assets/javascripts/lunr/lunr.pt.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Portuguese` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var j,C,r;e.pt=function(){this.pipeline.reset(),this.pipeline.add(e.pt.trimmer,e.pt.stopWordFilter,e.pt.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.pt.stemmer))},e.pt.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.pt.trimmer=e.trimmerSupport.generateTrimmer(e.pt.wordCharacters),e.Pipeline.registerFunction(e.pt.trimmer,"trimmer-pt"),e.pt.stemmer=(j=e.stemmerSupport.Among,C=e.stemmerSupport.SnowballProgram,r=new function(){var s,n,i,o=[new j("",-1,3),new j("ã",0,1),new j("õ",0,2)],a=[new j("",-1,3),new j("a~",0,1),new j("o~",0,2)],r=[new j("ic",-1,-1),new j("ad",-1,-1),new j("os",-1,-1),new j("iv",-1,1)],t=[new j("ante",-1,1),new j("avel",-1,1),new j("ível",-1,1)],u=[new j("ic",-1,1),new j("abil",-1,1),new j("iv",-1,1)],w=[new j("ica",-1,1),new j("ância",-1,1),new j("ência",-1,4),new j("ira",-1,9),new j("adora",-1,1),new j("osa",-1,1),new j("ista",-1,1),new j("iva",-1,8),new j("eza",-1,1),new j("logía",-1,2),new j("idade",-1,7),new j("ante",-1,1),new j("mente",-1,6),new j("amente",12,5),new j("ável",-1,1),new j("ível",-1,1),new j("ución",-1,3),new j("ico",-1,1),new j("ismo",-1,1),new j("oso",-1,1),new j("amento",-1,1),new j("imento",-1,1),new j("ivo",-1,8),new j("aça~o",-1,1),new j("ador",-1,1),new j("icas",-1,1),new j("ências",-1,4),new j("iras",-1,9),new j("adoras",-1,1),new j("osas",-1,1),new j("istas",-1,1),new j("ivas",-1,8),new j("ezas",-1,1),new j("logías",-1,2),new j("idades",-1,7),new j("uciones",-1,3),new j("adores",-1,1),new j("antes",-1,1),new j("aço~es",-1,1),new j("icos",-1,1),new j("ismos",-1,1),new j("osos",-1,1),new j("amentos",-1,1),new j("imentos",-1,1),new j("ivos",-1,8)],m=[new j("ada",-1,1),new j("ida",-1,1),new j("ia",-1,1),new j("aria",2,1),new j("eria",2,1),new j("iria",2,1),new j("ara",-1,1),new j("era",-1,1),new j("ira",-1,1),new j("ava",-1,1),new j("asse",-1,1),new j("esse",-1,1),new j("isse",-1,1),new j("aste",-1,1),new j("este",-1,1),new j("iste",-1,1),new j("ei",-1,1),new j("arei",16,1),new j("erei",16,1),new j("irei",16,1),new j("am",-1,1),new j("iam",20,1),new j("ariam",21,1),new j("eriam",21,1),new j("iriam",21,1),new j("aram",20,1),new j("eram",20,1),new j("iram",20,1),new j("avam",20,1),new j("em",-1,1),new j("arem",29,1),new j("erem",29,1),new j("irem",29,1),new j("assem",29,1),new j("essem",29,1),new j("issem",29,1),new j("ado",-1,1),new j("ido",-1,1),new j("ando",-1,1),new j("endo",-1,1),new j("indo",-1,1),new j("ara~o",-1,1),new j("era~o",-1,1),new j("ira~o",-1,1),new j("ar",-1,1),new j("er",-1,1),new j("ir",-1,1),new j("as",-1,1),new j("adas",47,1),new j("idas",47,1),new j("ias",47,1),new j("arias",50,1),new j("erias",50,1),new j("irias",50,1),new j("aras",47,1),new j("eras",47,1),new j("iras",47,1),new j("avas",47,1),new j("es",-1,1),new j("ardes",58,1),new j("erdes",58,1),new j("irdes",58,1),new j("ares",58,1),new j("eres",58,1),new j("ires",58,1),new j("asses",58,1),new j("esses",58,1),new j("isses",58,1),new j("astes",58,1),new j("estes",58,1),new j("istes",58,1),new j("is",-1,1),new j("ais",71,1),new j("eis",71,1),new j("areis",73,1),new j("ereis",73,1),new j("ireis",73,1),new j("áreis",73,1),new j("éreis",73,1),new j("íreis",73,1),new j("ásseis",73,1),new j("ésseis",73,1),new j("ísseis",73,1),new j("áveis",73,1),new j("íeis",73,1),new j("aríeis",84,1),new j("eríeis",84,1),new j("iríeis",84,1),new j("ados",-1,1),new j("idos",-1,1),new j("amos",-1,1),new j("áramos",90,1),new j("éramos",90,1),new j("íramos",90,1),new j("ávamos",90,1),new j("íamos",90,1),new j("aríamos",95,1),new j("eríamos",95,1),new j("iríamos",95,1),new j("emos",-1,1),new j("aremos",99,1),new j("eremos",99,1),new j("iremos",99,1),new j("ássemos",99,1),new j("êssemos",99,1),new j("íssemos",99,1),new j("imos",-1,1),new j("armos",-1,1),new j("ermos",-1,1),new j("irmos",-1,1),new j("ámos",-1,1),new j("arás",-1,1),new j("erás",-1,1),new j("irás",-1,1),new j("eu",-1,1),new j("iu",-1,1),new j("ou",-1,1),new j("ará",-1,1),new j("erá",-1,1),new j("irá",-1,1)],c=[new j("a",-1,1),new j("i",-1,1),new j("o",-1,1),new j("os",-1,1),new j("á",-1,1),new j("í",-1,1),new j("ó",-1,1)],l=[new j("e",-1,1),new j("ç",-1,2),new j("é",-1,1),new j("ê",-1,1)],f=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,3,19,12,2],d=new C;function v(){if(d.out_grouping(f,97,250)){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!0;d.cursor++}return!1}return!0}function p(){var e,r,s=d.cursor;if(d.in_grouping(f,97,250))if(e=d.cursor,v()){if(d.cursor=e,function(){if(d.in_grouping(f,97,250))for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return i=d.cursor,!0}())return}else i=d.cursor;if(d.cursor=s,d.out_grouping(f,97,250)){if(r=d.cursor,v()){if(d.cursor=r,!d.in_grouping(f,97,250)||d.cursor>=d.limit)return;d.cursor++}i=d.cursor}}function _(){for(;!d.in_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}for(;!d.out_grouping(f,97,250);){if(d.cursor>=d.limit)return!1;d.cursor++}return!0}function h(){return i<=d.cursor}function b(){return s<=d.cursor}function g(){var e;if(d.ket=d.cursor,!(e=d.find_among_b(w,45)))return!1;switch(d.bra=d.cursor,e){case 1:if(!b())return!1;d.slice_del();break;case 2:if(!b())return!1;d.slice_from("log");break;case 3:if(!b())return!1;d.slice_from("u");break;case 4:if(!b())return!1;d.slice_from("ente");break;case 5:if(!(n<=d.cursor))return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(r,4))&&(d.bra=d.cursor,b()&&(d.slice_del(),1==e&&(d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del()))));break;case 6:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(t,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 7:if(!b())return!1;d.slice_del(),d.ket=d.cursor,(e=d.find_among_b(u,3))&&(d.bra=d.cursor,1==e&&b()&&d.slice_del());break;case 8:if(!b())return!1;d.slice_del(),d.ket=d.cursor,d.eq_s_b(2,"at")&&(d.bra=d.cursor,b()&&d.slice_del());break;case 9:if(!h()||!d.eq_s_b(1,"e"))return!1;d.slice_from("ir")}return!0}function k(e,r){if(d.eq_s_b(1,e)){d.bra=d.cursor;var s=d.limit-d.cursor;if(d.eq_s_b(1,r))return d.cursor=d.limit-s,h()&&d.slice_del(),!1}return!0}function q(){if(!g()&&(d.cursor=d.limit,!function(){var e,r;if(d.cursor>=i){if(r=d.limit_backward,d.limit_backward=i,d.ket=d.cursor,e=d.find_among_b(m,120))return d.bra=d.cursor,1==e&&d.slice_del(),d.limit_backward=r,!0;d.limit_backward=r}return!1}()))return d.cursor=d.limit,d.ket=d.cursor,void((e=d.find_among_b(c,7))&&(d.bra=d.cursor,1==e&&h()&&d.slice_del()));var e;d.cursor=d.limit,d.ket=d.cursor,d.eq_s_b(1,"i")&&(d.bra=d.cursor,d.eq_s_b(1,"c")&&(d.cursor=d.limit,h()&&d.slice_del()))}this.setCurrent=function(e){d.setCurrent(e)},this.getCurrent=function(){return d.getCurrent()},this.stem=function(){var e,r=d.cursor;return function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(o,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("a~");continue;case 2:d.slice_from("o~");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),d.cursor=r,e=d.cursor,i=d.limit,s=n=i,p(),d.cursor=e,_()&&(n=d.cursor,_()&&(s=d.cursor)),d.limit_backward=r,d.cursor=d.limit,q(),d.cursor=d.limit,function(){var e;if(d.ket=d.cursor,e=d.find_among_b(l,4))switch(d.bra=d.cursor,e){case 1:h()&&(d.slice_del(),d.ket=d.cursor,d.limit,d.cursor,k("u","g")&&k("i","c"));break;case 2:d.slice_from("c")}}(),d.cursor=d.limit_backward,function(){for(var e;;){if(d.bra=d.cursor,e=d.find_among(a,3))switch(d.ket=d.cursor,e){case 1:d.slice_from("ã");continue;case 2:d.slice_from("õ");continue;case 3:if(d.cursor>=d.limit)break;d.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return r.setCurrent(e),r.stem(),r.getCurrent()}):(r.setCurrent(e),r.stem(),r.getCurrent())}),e.Pipeline.registerFunction(e.pt.stemmer,"stemmer-pt"),e.pt.stopWordFilter=e.generateStopWordFilter("a ao aos aquela aquelas aquele aqueles aquilo as até com como da das de dela delas dele deles depois do dos e ela elas ele eles em entre era eram essa essas esse esses esta estamos estas estava estavam este esteja estejam estejamos estes esteve estive estivemos estiver estivera estiveram estiverem estivermos estivesse estivessem estivéramos estivéssemos estou está estávamos estão eu foi fomos for fora foram forem formos fosse fossem fui fôramos fôssemos haja hajam hajamos havemos hei houve houvemos houver houvera houveram houverei houverem houveremos houveria houveriam houvermos houverá houverão houveríamos houvesse houvessem houvéramos houvéssemos há hão isso isto já lhe lhes mais mas me mesmo meu meus minha minhas muito na nas nem no nos nossa nossas nosso nossos num numa não nós o os ou para pela pelas pelo pelos por qual quando que quem se seja sejam sejamos sem serei seremos seria seriam será serão seríamos seu seus somos sou sua suas são só também te tem temos tenha tenham tenhamos tenho terei teremos teria teriam terá terão teríamos teu teus teve tinha tinham tive tivemos tiver tivera tiveram tiverem tivermos tivesse tivessem tivéramos tivéssemos tu tua tuas tém tínhamos um uma você vocês vos à às éramos".split(" ")),e.Pipeline.registerFunction(e.pt.stopWordFilter,"stopWordFilter-pt")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.ro.js b/assets/javascripts/lunr/lunr.ro.js new file mode 100644 index 00000000..c5ecc96c --- /dev/null +++ b/assets/javascripts/lunr/lunr.ro.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Romanian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,z,i;e.ro=function(){this.pipeline.reset(),this.pipeline.add(e.ro.trimmer,e.ro.stopWordFilter,e.ro.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ro.stemmer))},e.ro.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.ro.trimmer=e.trimmerSupport.generateTrimmer(e.ro.wordCharacters),e.Pipeline.registerFunction(e.ro.trimmer,"trimmer-ro"),e.ro.stemmer=(h=e.stemmerSupport.Among,z=e.stemmerSupport.SnowballProgram,i=new function(){var r,n,t,a,o=[new h("",-1,3),new h("I",0,1),new h("U",0,2)],s=[new h("ea",-1,3),new h("aţia",-1,7),new h("aua",-1,2),new h("iua",-1,4),new h("aţie",-1,7),new h("ele",-1,3),new h("ile",-1,5),new h("iile",6,4),new h("iei",-1,4),new h("atei",-1,6),new h("ii",-1,4),new h("ului",-1,1),new h("ul",-1,1),new h("elor",-1,3),new h("ilor",-1,4),new h("iilor",14,4)],c=[new h("icala",-1,4),new h("iciva",-1,4),new h("ativa",-1,5),new h("itiva",-1,6),new h("icale",-1,4),new h("aţiune",-1,5),new h("iţiune",-1,6),new h("atoare",-1,5),new h("itoare",-1,6),new h("ătoare",-1,5),new h("icitate",-1,4),new h("abilitate",-1,1),new h("ibilitate",-1,2),new h("ivitate",-1,3),new h("icive",-1,4),new h("ative",-1,5),new h("itive",-1,6),new h("icali",-1,4),new h("atori",-1,5),new h("icatori",18,4),new h("itori",-1,6),new h("ători",-1,5),new h("icitati",-1,4),new h("abilitati",-1,1),new h("ivitati",-1,3),new h("icivi",-1,4),new h("ativi",-1,5),new h("itivi",-1,6),new h("icităi",-1,4),new h("abilităi",-1,1),new h("ivităi",-1,3),new h("icităţi",-1,4),new h("abilităţi",-1,1),new h("ivităţi",-1,3),new h("ical",-1,4),new h("ator",-1,5),new h("icator",35,4),new h("itor",-1,6),new h("ător",-1,5),new h("iciv",-1,4),new h("ativ",-1,5),new h("itiv",-1,6),new h("icală",-1,4),new h("icivă",-1,4),new h("ativă",-1,5),new h("itivă",-1,6)],u=[new h("ica",-1,1),new h("abila",-1,1),new h("ibila",-1,1),new h("oasa",-1,1),new h("ata",-1,1),new h("ita",-1,1),new h("anta",-1,1),new h("ista",-1,3),new h("uta",-1,1),new h("iva",-1,1),new h("ic",-1,1),new h("ice",-1,1),new h("abile",-1,1),new h("ibile",-1,1),new h("isme",-1,3),new h("iune",-1,2),new h("oase",-1,1),new h("ate",-1,1),new h("itate",17,1),new h("ite",-1,1),new h("ante",-1,1),new h("iste",-1,3),new h("ute",-1,1),new h("ive",-1,1),new h("ici",-1,1),new h("abili",-1,1),new h("ibili",-1,1),new h("iuni",-1,2),new h("atori",-1,1),new h("osi",-1,1),new h("ati",-1,1),new h("itati",30,1),new h("iti",-1,1),new h("anti",-1,1),new h("isti",-1,3),new h("uti",-1,1),new h("işti",-1,3),new h("ivi",-1,1),new h("ităi",-1,1),new h("oşi",-1,1),new h("ităţi",-1,1),new h("abil",-1,1),new h("ibil",-1,1),new h("ism",-1,3),new h("ator",-1,1),new h("os",-1,1),new h("at",-1,1),new h("it",-1,1),new h("ant",-1,1),new h("ist",-1,3),new h("ut",-1,1),new h("iv",-1,1),new h("ică",-1,1),new h("abilă",-1,1),new h("ibilă",-1,1),new h("oasă",-1,1),new h("ată",-1,1),new h("ită",-1,1),new h("antă",-1,1),new h("istă",-1,3),new h("ută",-1,1),new h("ivă",-1,1)],w=[new h("ea",-1,1),new h("ia",-1,1),new h("esc",-1,1),new h("ăsc",-1,1),new h("ind",-1,1),new h("ând",-1,1),new h("are",-1,1),new h("ere",-1,1),new h("ire",-1,1),new h("âre",-1,1),new h("se",-1,2),new h("ase",10,1),new h("sese",10,2),new h("ise",10,1),new h("use",10,1),new h("âse",10,1),new h("eşte",-1,1),new h("ăşte",-1,1),new h("eze",-1,1),new h("ai",-1,1),new h("eai",19,1),new h("iai",19,1),new h("sei",-1,2),new h("eşti",-1,1),new h("ăşti",-1,1),new h("ui",-1,1),new h("ezi",-1,1),new h("âi",-1,1),new h("aşi",-1,1),new h("seşi",-1,2),new h("aseşi",29,1),new h("seseşi",29,2),new h("iseşi",29,1),new h("useşi",29,1),new h("âseşi",29,1),new h("işi",-1,1),new h("uşi",-1,1),new h("âşi",-1,1),new h("aţi",-1,2),new h("eaţi",38,1),new h("iaţi",38,1),new h("eţi",-1,2),new h("iţi",-1,2),new h("âţi",-1,2),new h("arăţi",-1,1),new h("serăţi",-1,2),new h("aserăţi",45,1),new h("seserăţi",45,2),new h("iserăţi",45,1),new h("userăţi",45,1),new h("âserăţi",45,1),new h("irăţi",-1,1),new h("urăţi",-1,1),new h("ârăţi",-1,1),new h("am",-1,1),new h("eam",54,1),new h("iam",54,1),new h("em",-1,2),new h("asem",57,1),new h("sesem",57,2),new h("isem",57,1),new h("usem",57,1),new h("âsem",57,1),new h("im",-1,2),new h("âm",-1,2),new h("ăm",-1,2),new h("arăm",65,1),new h("serăm",65,2),new h("aserăm",67,1),new h("seserăm",67,2),new h("iserăm",67,1),new h("userăm",67,1),new h("âserăm",67,1),new h("irăm",65,1),new h("urăm",65,1),new h("ârăm",65,1),new h("au",-1,1),new h("eau",76,1),new h("iau",76,1),new h("indu",-1,1),new h("ându",-1,1),new h("ez",-1,1),new h("ească",-1,1),new h("ară",-1,1),new h("seră",-1,2),new h("aseră",84,1),new h("seseră",84,2),new h("iseră",84,1),new h("useră",84,1),new h("âseră",84,1),new h("iră",-1,1),new h("ură",-1,1),new h("âră",-1,1),new h("ează",-1,1)],i=[new h("a",-1,1),new h("e",-1,1),new h("ie",1,1),new h("i",-1,1),new h("ă",-1,1)],m=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,2,32,0,0,4],l=new z;function f(e,i){l.eq_s(1,e)&&(l.ket=l.cursor,l.in_grouping(m,97,259)&&l.slice_from(i))}function p(){if(l.out_grouping(m,97,259)){for(;!l.in_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}return!0}function d(){var e,i,r=l.cursor;if(l.in_grouping(m,97,259)){if(e=l.cursor,!p())return void(a=l.cursor);if(l.cursor=e,!function(){if(l.in_grouping(m,97,259))for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!0;l.cursor++}return!1}())return void(a=l.cursor)}l.cursor=r,l.out_grouping(m,97,259)&&(i=l.cursor,p()&&(l.cursor=i,l.in_grouping(m,97,259)&&l.cursor=l.limit)return!1;l.cursor++}for(;!l.out_grouping(m,97,259);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function v(){return t<=l.cursor}function _(){var e,i=l.limit-l.cursor;if(l.ket=l.cursor,(e=l.find_among_b(c,46))&&(l.bra=l.cursor,v())){switch(e){case 1:l.slice_from("abil");break;case 2:l.slice_from("ibil");break;case 3:l.slice_from("iv");break;case 4:l.slice_from("ic");break;case 5:l.slice_from("at");break;case 6:l.slice_from("it")}return r=!0,l.cursor=l.limit-i,!0}return!1}function g(){var e,i;for(r=!1;;)if(i=l.limit-l.cursor,!_()){l.cursor=l.limit-i;break}if(l.ket=l.cursor,(e=l.find_among_b(u,62))&&(l.bra=l.cursor,n<=l.cursor)){switch(e){case 1:l.slice_del();break;case 2:l.eq_s_b(1,"ţ")&&(l.bra=l.cursor,l.slice_from("t"));break;case 3:l.slice_from("ist")}r=!0}}function k(){var e;l.ket=l.cursor,(e=l.find_among_b(i,5))&&(l.bra=l.cursor,a<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){var e,i=l.cursor;return function(){for(var e,i;e=l.cursor,l.in_grouping(m,97,259)&&(i=l.cursor,l.bra=i,f("u","U"),l.cursor=i,f("i","I")),l.cursor=e,!(l.cursor>=l.limit);)l.cursor++}(),l.cursor=i,e=l.cursor,a=l.limit,n=t=a,d(),l.cursor=e,b()&&(t=l.cursor,b()&&(n=l.cursor)),l.limit_backward=i,l.cursor=l.limit,function(){var e,i;if(l.ket=l.cursor,(e=l.find_among_b(s,16))&&(l.bra=l.cursor,v()))switch(e){case 1:l.slice_del();break;case 2:l.slice_from("a");break;case 3:l.slice_from("e");break;case 4:l.slice_from("i");break;case 5:i=l.limit-l.cursor,l.eq_s_b(2,"ab")||(l.cursor=l.limit-i,l.slice_from("i"));break;case 6:l.slice_from("at");break;case 7:l.slice_from("aţi")}}(),l.cursor=l.limit,g(),l.cursor=l.limit,r||(l.cursor=l.limit,function(){var e,i,r;if(l.cursor>=a){if(i=l.limit_backward,l.limit_backward=a,l.ket=l.cursor,e=l.find_among_b(w,94))switch(l.bra=l.cursor,e){case 1:if(r=l.limit-l.cursor,!l.out_grouping_b(m,97,259)&&(l.cursor=l.limit-r,!l.eq_s_b(1,"u")))break;case 2:l.slice_del()}l.limit_backward=i}}(),l.cursor=l.limit),k(),l.cursor=l.limit_backward,function(){for(var e;;){if(l.bra=l.cursor,e=l.find_among(o,3))switch(l.ket=l.cursor,e){case 1:l.slice_from("i");continue;case 2:l.slice_from("u");continue;case 3:if(l.cursor>=l.limit)break;l.cursor++;continue}break}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.ro.stemmer,"stemmer-ro"),e.ro.stopWordFilter=e.generateStopWordFilter("acea aceasta această aceea acei aceia acel acela acele acelea acest acesta aceste acestea aceşti aceştia acolo acord acum ai aia aibă aici al ale alea altceva altcineva am ar are asemenea asta astea astăzi asupra au avea avem aveţi azi aş aşadar aţi bine bucur bună ca care caut ce cel ceva chiar cinci cine cineva contra cu cum cumva curând curînd când cât câte câtva câţi cînd cît cîte cîtva cîţi că căci cărei căror cărui către da dacă dar datorită dată dau de deci deja deoarece departe deşi din dinaintea dintr- dintre doi doilea două drept după dă ea ei el ele eram este eu eşti face fata fi fie fiecare fii fim fiu fiţi frumos fără graţie halbă iar ieri la le li lor lui lângă lîngă mai mea mei mele mereu meu mi mie mine mult multă mulţi mulţumesc mâine mîine mă ne nevoie nici nicăieri nimeni nimeri nimic nişte noastre noastră noi noroc nostru nouă noştri nu opt ori oricare orice oricine oricum oricând oricât oricînd oricît oriunde patra patru patrulea pe pentru peste pic poate pot prea prima primul prin puţin puţina puţină până pînă rog sa sale sau se spate spre sub sunt suntem sunteţi sută sînt sîntem sînteţi să săi său ta tale te timp tine toate toată tot totuşi toţi trei treia treilea tu tăi tău un una unde undeva unei uneia unele uneori unii unor unora unu unui unuia unul vi voastre voastră voi vostru vouă voştri vreme vreo vreun vă zece zero zi zice îi îl îmi împotriva în înainte înaintea încotro încât încît între întrucât întrucît îţi ăla ălea ăsta ăstea ăştia şapte şase şi ştiu ţi ţie".split(" ")),e.Pipeline.registerFunction(e.ro.stopWordFilter,"stopWordFilter-ro")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.ru.js b/assets/javascripts/lunr/lunr.ru.js new file mode 100644 index 00000000..104bc6e8 --- /dev/null +++ b/assets/javascripts/lunr/lunr.ru.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Russian` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():n()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var h,g,n;e.ru=function(){this.pipeline.reset(),this.pipeline.add(e.ru.trimmer,e.ru.stopWordFilter,e.ru.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ru.stemmer))},e.ru.wordCharacters="Ѐ-҄҇-ԯᴫᵸⷠ-ⷿꙀ-ꚟ︮︯",e.ru.trimmer=e.trimmerSupport.generateTrimmer(e.ru.wordCharacters),e.Pipeline.registerFunction(e.ru.trimmer,"trimmer-ru"),e.ru.stemmer=(h=e.stemmerSupport.Among,g=e.stemmerSupport.SnowballProgram,n=new function(){var n,e,r=[new h("в",-1,1),new h("ив",0,2),new h("ыв",0,2),new h("вши",-1,1),new h("ивши",3,2),new h("ывши",3,2),new h("вшись",-1,1),new h("ившись",6,2),new h("ывшись",6,2)],t=[new h("ее",-1,1),new h("ие",-1,1),new h("ое",-1,1),new h("ые",-1,1),new h("ими",-1,1),new h("ыми",-1,1),new h("ей",-1,1),new h("ий",-1,1),new h("ой",-1,1),new h("ый",-1,1),new h("ем",-1,1),new h("им",-1,1),new h("ом",-1,1),new h("ым",-1,1),new h("его",-1,1),new h("ого",-1,1),new h("ему",-1,1),new h("ому",-1,1),new h("их",-1,1),new h("ых",-1,1),new h("ею",-1,1),new h("ою",-1,1),new h("ую",-1,1),new h("юю",-1,1),new h("ая",-1,1),new h("яя",-1,1)],w=[new h("ем",-1,1),new h("нн",-1,1),new h("вш",-1,1),new h("ивш",2,2),new h("ывш",2,2),new h("щ",-1,1),new h("ющ",5,1),new h("ующ",6,2)],i=[new h("сь",-1,1),new h("ся",-1,1)],u=[new h("ла",-1,1),new h("ила",0,2),new h("ыла",0,2),new h("на",-1,1),new h("ена",3,2),new h("ете",-1,1),new h("ите",-1,2),new h("йте",-1,1),new h("ейте",7,2),new h("уйте",7,2),new h("ли",-1,1),new h("или",10,2),new h("ыли",10,2),new h("й",-1,1),new h("ей",13,2),new h("уй",13,2),new h("л",-1,1),new h("ил",16,2),new h("ыл",16,2),new h("ем",-1,1),new h("им",-1,2),new h("ым",-1,2),new h("н",-1,1),new h("ен",22,2),new h("ло",-1,1),new h("ило",24,2),new h("ыло",24,2),new h("но",-1,1),new h("ено",27,2),new h("нно",27,1),new h("ет",-1,1),new h("ует",30,2),new h("ит",-1,2),new h("ыт",-1,2),new h("ют",-1,1),new h("уют",34,2),new h("ят",-1,2),new h("ны",-1,1),new h("ены",37,2),new h("ть",-1,1),new h("ить",39,2),new h("ыть",39,2),new h("ешь",-1,1),new h("ишь",-1,2),new h("ю",-1,2),new h("ую",44,2)],s=[new h("а",-1,1),new h("ев",-1,1),new h("ов",-1,1),new h("е",-1,1),new h("ие",3,1),new h("ье",3,1),new h("и",-1,1),new h("еи",6,1),new h("ии",6,1),new h("ами",6,1),new h("ями",6,1),new h("иями",10,1),new h("й",-1,1),new h("ей",12,1),new h("ией",13,1),new h("ий",12,1),new h("ой",12,1),new h("ам",-1,1),new h("ем",-1,1),new h("ием",18,1),new h("ом",-1,1),new h("ям",-1,1),new h("иям",21,1),new h("о",-1,1),new h("у",-1,1),new h("ах",-1,1),new h("ях",-1,1),new h("иях",26,1),new h("ы",-1,1),new h("ь",-1,1),new h("ю",-1,1),new h("ию",30,1),new h("ью",30,1),new h("я",-1,1),new h("ия",33,1),new h("ья",33,1)],o=[new h("ост",-1,1),new h("ость",-1,1)],c=[new h("ейше",-1,1),new h("н",-1,2),new h("ейш",-1,1),new h("ь",-1,3)],m=[33,65,8,232],l=new g;function f(){for(;!l.in_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function a(){for(;!l.out_grouping(m,1072,1103);){if(l.cursor>=l.limit)return!1;l.cursor++}return!0}function p(e,n){var r,t;if(l.ket=l.cursor,r=l.find_among_b(e,n)){switch(l.bra=l.cursor,r){case 1:if(t=l.limit-l.cursor,!l.eq_s_b(1,"а")&&(l.cursor=l.limit-t,!l.eq_s_b(1,"я")))return!1;case 2:l.slice_del()}return!0}return!1}function d(e,n){var r;return l.ket=l.cursor,!!(r=l.find_among_b(e,n))&&(l.bra=l.cursor,1==r&&l.slice_del(),!0)}function _(){return!!d(t,26)&&(p(w,8),!0)}function b(){var e;l.ket=l.cursor,(e=l.find_among_b(o,2))&&(l.bra=l.cursor,n<=l.cursor&&1==e&&l.slice_del())}this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){return e=l.limit,n=e,f()&&(e=l.cursor,a()&&f()&&a()&&(n=l.cursor)),l.cursor=l.limit,!(l.cursor>3]&1<<(7&s))return this.cursor++,!0}return!1},in_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(s<=i&&t<=s&&r[(s-=t)>>3]&1<<(7&s))return this.cursor--,!0}return!1},out_grouping:function(r,t,i){if(this.cursor>3]&1<<(7&s)))return this.cursor++,!0}return!1},out_grouping_b:function(r,t,i){if(this.cursor>this.limit_backward){var s=b.charCodeAt(this.cursor-1);if(i>3]&1<<(7&s)))return this.cursor--,!0}return!1},eq_s:function(r,t){if(this.limit-this.cursor>1),a=0,f=u=(l=r[i]).s_size){if(this.cursor=e+l.s_size,!l.method)return l.result;var m=l.method();if(this.cursor=e+l.s_size,m)return l.result}if((i=l.substring_i)<0)return 0}},find_among_b:function(r,t){for(var i=0,s=t,e=this.cursor,n=this.limit_backward,u=0,o=0,h=!1;;){for(var c=i+(s-i>>1),a=0,f=u=(_=r[i]).s_size){if(this.cursor=e-_.s_size,!_.method)return _.result;var m=_.method();if(this.cursor=e-_.s_size,m)return _.result}if((i=_.substring_i)<0)return 0}},replace_s:function(r,t,i){var s=i.length-(t-r);return b=b.substring(0,r)+i+b.substring(t),this.limit+=s,this.cursor>=t?this.cursor+=s:this.cursor>r&&(this.cursor=r),s},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>b.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),b.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.sv.js b/assets/javascripts/lunr/lunr.sv.js new file mode 100644 index 00000000..a46a4e70 --- /dev/null +++ b/assets/javascripts/lunr/lunr.sv.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Swedish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,l,n;e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=(r=e.stemmerSupport.Among,l=e.stemmerSupport.SnowballProgram,n=new function(){var n,t,i=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],s=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],o=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],u=[119,127,149],m=new l;this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var e,r=m.cursor;return function(){var e,r=m.cursor+3;if(t=m.limit,0<=r||r<=m.limit){for(n=r;;){if(e=m.cursor,m.in_grouping(o,97,246)){m.cursor=e;break}if(m.cursor=e,m.cursor>=m.limit)return;m.cursor++}for(;!m.out_grouping(o,97,246);){if(m.cursor>=m.limit)return;m.cursor++}(t=m.cursor)=t&&(m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(i,37),m.limit_backward=r,e))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.in_grouping_b(u,98,121)&&m.slice_del()}}(),m.cursor=m.limit,e=m.limit_backward,m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.find_among_b(s,7)&&(m.cursor=m.limit,m.ket=m.cursor,m.cursor>m.limit_backward&&(m.bra=--m.cursor,m.slice_del())),m.limit_backward=e),m.cursor=m.limit,function(){var e,r;if(m.cursor>=t){if(r=m.limit_backward,m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(a,5))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.slice_from("lös");break;case 3:m.slice_from("full")}m.limit_backward=r}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.th.js b/assets/javascripts/lunr/lunr.th.js new file mode 100644 index 00000000..7f9887f7 --- /dev/null +++ b/assets/javascripts/lunr/lunr.th.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Thai` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2017, Keerati Thiwanruk + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(t){if(void 0===t)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===t.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==t.version[0];t.th=function(){this.pipeline.reset(),this.pipeline.add(t.th.trimmer),i?this.tokenizer=t.th.tokenizer:(t.tokenizer&&(t.tokenizer=t.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=t.th.tokenizer))},t.th.wordCharacters="[฀-๿]",t.th.trimmer=t.trimmerSupport.generateTrimmer(t.th.wordCharacters),t.Pipeline.registerFunction(t.th.trimmer,"trimmer-th");var n=t.wordcut;n.init(),t.th.tokenizer=function(e){if(!arguments.length||null==e||null==e)return[];if(Array.isArray(e))return e.map(function(e){return i?new t.Token(e):e});var r=e.toString().replace(/^\s+/,"");return n.cut(r).split("|")}}}); \ No newline at end of file diff --git a/assets/javascripts/lunr/lunr.tr.js b/assets/javascripts/lunr/lunr.tr.js new file mode 100644 index 00000000..64ba95cb --- /dev/null +++ b/assets/javascripts/lunr/lunr.tr.js @@ -0,0 +1,17 @@ +/*! + * Lunr languages, `Turkish` language + * https://github.com/MihaiValentin/lunr-languages + * + * Copyright 2014, Mihai Valentin + * http://www.mozilla.org/MPL/ + */ +/*! + * based on + * Snowball JavaScript Library v0.3 + * http://code.google.com/p/urim/ + * http://snowball.tartarus.org/ + * + * Copyright 2010, Oleg Mazko + * http://www.mozilla.org/MPL/ + */ +!function(r,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(r.lunr)}(this,function(){return function(r){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var mr,dr,i;r.tr=function(){this.pipeline.reset(),this.pipeline.add(r.tr.trimmer,r.tr.stopWordFilter,r.tr.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(r.tr.stemmer))},r.tr.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",r.tr.trimmer=r.trimmerSupport.generateTrimmer(r.tr.wordCharacters),r.Pipeline.registerFunction(r.tr.trimmer,"trimmer-tr"),r.tr.stemmer=(mr=r.stemmerSupport.Among,dr=r.stemmerSupport.SnowballProgram,i=new function(){var t,r=[new mr("m",-1,-1),new mr("n",-1,-1),new mr("miz",-1,-1),new mr("niz",-1,-1),new mr("muz",-1,-1),new mr("nuz",-1,-1),new mr("müz",-1,-1),new mr("nüz",-1,-1),new mr("mız",-1,-1),new mr("nız",-1,-1)],i=[new mr("leri",-1,-1),new mr("ları",-1,-1)],e=[new mr("ni",-1,-1),new mr("nu",-1,-1),new mr("nü",-1,-1),new mr("nı",-1,-1)],n=[new mr("in",-1,-1),new mr("un",-1,-1),new mr("ün",-1,-1),new mr("ın",-1,-1)],u=[new mr("a",-1,-1),new mr("e",-1,-1)],o=[new mr("na",-1,-1),new mr("ne",-1,-1)],s=[new mr("da",-1,-1),new mr("ta",-1,-1),new mr("de",-1,-1),new mr("te",-1,-1)],c=[new mr("nda",-1,-1),new mr("nde",-1,-1)],l=[new mr("dan",-1,-1),new mr("tan",-1,-1),new mr("den",-1,-1),new mr("ten",-1,-1)],a=[new mr("ndan",-1,-1),new mr("nden",-1,-1)],m=[new mr("la",-1,-1),new mr("le",-1,-1)],d=[new mr("ca",-1,-1),new mr("ce",-1,-1)],f=[new mr("im",-1,-1),new mr("um",-1,-1),new mr("üm",-1,-1),new mr("ım",-1,-1)],b=[new mr("sin",-1,-1),new mr("sun",-1,-1),new mr("sün",-1,-1),new mr("sın",-1,-1)],w=[new mr("iz",-1,-1),new mr("uz",-1,-1),new mr("üz",-1,-1),new mr("ız",-1,-1)],_=[new mr("siniz",-1,-1),new mr("sunuz",-1,-1),new mr("sünüz",-1,-1),new mr("sınız",-1,-1)],k=[new mr("lar",-1,-1),new mr("ler",-1,-1)],p=[new mr("niz",-1,-1),new mr("nuz",-1,-1),new mr("nüz",-1,-1),new mr("nız",-1,-1)],g=[new mr("dir",-1,-1),new mr("tir",-1,-1),new mr("dur",-1,-1),new mr("tur",-1,-1),new mr("dür",-1,-1),new mr("tür",-1,-1),new mr("dır",-1,-1),new mr("tır",-1,-1)],y=[new mr("casına",-1,-1),new mr("cesine",-1,-1)],z=[new mr("di",-1,-1),new mr("ti",-1,-1),new mr("dik",-1,-1),new mr("tik",-1,-1),new mr("duk",-1,-1),new mr("tuk",-1,-1),new mr("dük",-1,-1),new mr("tük",-1,-1),new mr("dık",-1,-1),new mr("tık",-1,-1),new mr("dim",-1,-1),new mr("tim",-1,-1),new mr("dum",-1,-1),new mr("tum",-1,-1),new mr("düm",-1,-1),new mr("tüm",-1,-1),new mr("dım",-1,-1),new mr("tım",-1,-1),new mr("din",-1,-1),new mr("tin",-1,-1),new mr("dun",-1,-1),new mr("tun",-1,-1),new mr("dün",-1,-1),new mr("tün",-1,-1),new mr("dın",-1,-1),new mr("tın",-1,-1),new mr("du",-1,-1),new mr("tu",-1,-1),new mr("dü",-1,-1),new mr("tü",-1,-1),new mr("dı",-1,-1),new mr("tı",-1,-1)],h=[new mr("sa",-1,-1),new mr("se",-1,-1),new mr("sak",-1,-1),new mr("sek",-1,-1),new mr("sam",-1,-1),new mr("sem",-1,-1),new mr("san",-1,-1),new mr("sen",-1,-1)],v=[new mr("miş",-1,-1),new mr("muş",-1,-1),new mr("müş",-1,-1),new mr("mış",-1,-1)],q=[new mr("b",-1,1),new mr("c",-1,2),new mr("d",-1,3),new mr("ğ",-1,4)],C=[17,65,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,8,0,0,0,0,0,0,1],P=[1,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,1],F=[65],S=[65],W=[["a",[1,64,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["e",[17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130],101,252],["ı",[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],97,305],["i",[17],101,105],["o",F,111,117],["ö",S,246,252],["u",F,111,117]],L=new dr;function x(r,i,e){for(;;){var n=L.limit-L.cursor;if(L.in_grouping_b(r,i,e)){L.cursor=L.limit-n;break}if(L.cursor=L.limit-n,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}function A(){var r,i;r=L.limit-L.cursor,x(C,97,305);for(var e=0;eL.limit_backward&&(L.cursor--,e=L.limit-L.cursor,i()))?(L.cursor=L.limit-e,!0):(L.cursor=L.limit-n,r()?(L.cursor=L.limit-n,!1):(L.cursor=L.limit-n,!(L.cursor<=L.limit_backward)&&(L.cursor--,!!i()&&(L.cursor=L.limit-n,!0))))}function j(r){return E(r,function(){return L.in_grouping_b(C,97,305)})}function T(){return j(function(){return L.eq_s_b(1,"n")})}function Z(){return j(function(){return L.eq_s_b(1,"y")})}function B(){return L.find_among_b(r,10)&&E(function(){return L.in_grouping_b(P,105,305)},function(){return L.out_grouping_b(C,97,305)})}function D(){return A()&&L.in_grouping_b(P,105,305)&&j(function(){return L.eq_s_b(1,"s")})}function G(){return L.find_among_b(i,2)}function H(){return A()&&L.find_among_b(n,4)&&T()}function I(){return A()&&L.find_among_b(s,4)}function J(){return A()&&L.find_among_b(c,2)}function K(){return A()&&L.find_among_b(f,4)&&Z()}function M(){return A()&&L.find_among_b(b,4)}function N(){return A()&&L.find_among_b(w,4)&&Z()}function O(){return L.find_among_b(_,4)}function Q(){return A()&&L.find_among_b(k,2)}function R(){return A()&&L.find_among_b(g,8)}function U(){return A()&&L.find_among_b(z,32)&&Z()}function V(){return L.find_among_b(h,8)&&Z()}function X(){return A()&&L.find_among_b(v,4)&&Z()}function Y(){var r=L.limit-L.cursor;return!(X()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,L.eq_s_b(3,"ken")&&Z()))))}function $(){if(L.find_among_b(y,2)){var r=L.limit-L.cursor;if(O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X())return!1}return!0}function rr(){if(!A()||!L.find_among_b(p,4))return!0;var r=L.limit-L.cursor;return!U()&&(L.cursor=L.limit-r,!V())}function ir(){var r,i,e,n=L.limit-L.cursor;if(L.ket=L.cursor,t=!0,Y()&&(L.cursor=L.limit-n,$()&&(L.cursor=L.limit-n,function(){if(Q()){L.bra=L.cursor,L.slice_del();var r=L.limit-L.cursor;return L.ket=L.cursor,R()||(L.cursor=L.limit-r,U()||(L.cursor=L.limit-r,V()||(L.cursor=L.limit-r,X()||(L.cursor=L.limit-r)))),t=!1}return!0}()&&(L.cursor=L.limit-n,rr()&&(L.cursor=L.limit-n,e=L.limit-L.cursor,!(O()||(L.cursor=L.limit-e,N()||(L.cursor=L.limit-e,M()||(L.cursor=L.limit-e,K()))))||(L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,X()||(L.cursor=L.limit-i),0)))))){if(L.cursor=L.limit-n,!R())return;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,O()||(L.cursor=L.limit-r,Q()||(L.cursor=L.limit-r,K()||(L.cursor=L.limit-r,M()||(L.cursor=L.limit-r,N()||(L.cursor=L.limit-r))))),X()||(L.cursor=L.limit-r)}L.bra=L.cursor,L.slice_del()}function er(){var r,i,e,n;if(L.ket=L.cursor,L.eq_s_b(2,"ki")){if(r=L.limit-L.cursor,I())return L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()?(L.bra=L.cursor,L.slice_del(),er()):(L.cursor=L.limit-i,B()&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))),!0;if(L.cursor=L.limit-r,H()){if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,e=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-e,L.ket=L.cursor,!B()&&(L.cursor=L.limit-e,!D()&&(L.cursor=L.limit-e,!er())))return!0;L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}return!0}if(L.cursor=L.limit-r,J()){if(n=L.limit-L.cursor,G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-n,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-n,!er())return!1;return!0}}return!1}function nr(r){if(L.ket=L.cursor,!J()&&(L.cursor=L.limit-r,!A()||!L.find_among_b(o,2)))return!1;var i=L.limit-L.cursor;if(G())L.bra=L.cursor,L.slice_del();else if(L.cursor=L.limit-i,D())L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er());else if(L.cursor=L.limit-i,!er())return!1;return!0}function tr(r){if(L.ket=L.cursor,!(A()&&L.find_among_b(a,2)||(L.cursor=L.limit-r,A()&&L.find_among_b(e,4))))return!1;var i=L.limit-L.cursor;return!(!D()&&(L.cursor=L.limit-i,!G()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()),!0)}function ur(){var r,i=L.limit-L.cursor;return L.ket=L.cursor,!!(H()||(L.cursor=L.limit-i,A()&&L.find_among_b(m,2)&&Z()))&&(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,!(!Q()||(L.bra=L.cursor,L.slice_del(),!er()))||(L.cursor=L.limit-r,L.ket=L.cursor,(B()||(L.cursor=L.limit-r,D()||(L.cursor=L.limit-r,er())))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())),!0))}function or(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,!(I()||(L.cursor=L.limit-e,A()&&L.in_grouping_b(P,105,305)&&Z()||(L.cursor=L.limit-e,A()&&L.find_among_b(u,2)&&Z()))))return!1;if(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,r=L.limit-L.cursor,B())L.bra=L.cursor,L.slice_del(),i=L.limit-L.cursor,L.ket=L.cursor,Q()||(L.cursor=L.limit-i);else if(L.cursor=L.limit-r,!Q())return!0;return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,er(),!0}function sr(){var r,i,e=L.limit-L.cursor;if(L.ket=L.cursor,Q())return L.bra=L.cursor,L.slice_del(),void er();if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(d,2)&&T())if(L.bra=L.cursor,L.slice_del(),r=L.limit-L.cursor,L.ket=L.cursor,G())L.bra=L.cursor,L.slice_del();else{if(L.cursor=L.limit-r,L.ket=L.cursor,!B()&&(L.cursor=L.limit-r,!D())){if(L.cursor=L.limit-r,L.ket=L.cursor,!Q())return;if(L.bra=L.cursor,L.slice_del(),!er())return}L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())}else if(L.cursor=L.limit-e,!nr(e)&&(L.cursor=L.limit-e,!tr(e))){if(L.cursor=L.limit-e,L.ket=L.cursor,A()&&L.find_among_b(l,4))return L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,i=L.limit-L.cursor,void(B()?(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er())):(L.cursor=L.limit-i,Q()?(L.bra=L.cursor,L.slice_del()):L.cursor=L.limit-i,er()));if(L.cursor=L.limit-e,!ur()){if(L.cursor=L.limit-e,G())return L.bra=L.cursor,void L.slice_del();L.cursor=L.limit-e,er()||(L.cursor=L.limit-e,or()||(L.cursor=L.limit-e,L.ket=L.cursor,(B()||(L.cursor=L.limit-e,D()))&&(L.bra=L.cursor,L.slice_del(),L.ket=L.cursor,Q()&&(L.bra=L.cursor,L.slice_del(),er()))))}}}function cr(r,i,e){if(L.cursor=L.limit-r,function(){for(;;){var r=L.limit-L.cursor;if(L.in_grouping_b(C,97,305)){L.cursor=L.limit-r;break}if(L.cursor=L.limit-r,L.cursor<=L.limit_backward)return!1;L.cursor--}return!0}()){var n=L.limit-L.cursor;if(!L.eq_s_b(1,i)&&(L.cursor=L.limit-n,!L.eq_s_b(1,e)))return!0;L.cursor=L.limit-r;var t=L.cursor;return L.insert(L.cursor,L.cursor,e),L.cursor=t,!1}return!0}function lr(r,i,e){for(;!L.eq_s(i,e);){if(L.cursor>=L.limit)return!0;L.cursor++}return i!=L.limit||(L.cursor=r,!1)}function ar(){var r,i,e=L.cursor;return!(!lr(r=L.cursor,2,"ad")||!lr(L.cursor=r,5,"soyad"))&&(L.limit_backward=e,L.cursor=L.limit,i=L.limit-L.cursor,(L.eq_s_b(1,"d")||(L.cursor=L.limit-i,L.eq_s_b(1,"g")))&&cr(i,"a","ı")&&cr(i,"e","i")&&cr(i,"o","u")&&cr(i,"ö","ü"),L.cursor=L.limit,function(){var r;if(L.ket=L.cursor,r=L.find_among_b(q,4))switch(L.bra=L.cursor,r){case 1:L.slice_from("p");break;case 2:L.slice_from("ç");break;case 3:L.slice_from("t");break;case 4:L.slice_from("k")}}(),!0)}this.setCurrent=function(r){L.setCurrent(r)},this.getCurrent=function(){return L.getCurrent()},this.stem=function(){return!!(function(){for(var r,i=L.cursor,e=2;;){for(r=L.cursor;!L.in_grouping(C,97,305);){if(L.cursor>=L.limit)return L.cursor=r,!(0e&&(this._events[n].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[n].length),"function"==typeof console.trace&&console.trace()));return this},r.prototype.once=function(n,t){if(!a(t))throw TypeError("listener must be a function");var e=!1;function r(){this.removeListener(n,r),e||(e=!0,t.apply(this,arguments))}return r.listener=t,this.on(n,r),this},r.prototype.removeListener=function(n,t){var e,r,i,o;if(!a(t))throw TypeError("listener must be a function");if(!this._events||!this._events[n])return this;if(i=(e=this._events[n]).length,r=-1,e===t||a(e.listener)&&e.listener===t)delete this._events[n],this._events.removeListener&&this.emit("removeListener",n,t);else if(c(e)){for(o=i;0this.maxLength)return i();if(!this.stat&&p(this.cache,o)){var t=this.cache[o];if(Array.isArray(t)&&(t="DIR"),!n||"DIR"===t)return i(null,t);if(n&&"FILE"===t)return i()}var e=this.statCache[o];if(void 0!==e){if(!1===e)return i(null,e);var s=e.isDirectory()?"DIR":"FILE";return n&&"FILE"===s?i():i(null,s,e)}var a=this,c=d("stat\0"+o,function(n,e){{if(e&&e.isSymbolicLink())return u.stat(o,function(n,t){n?a._stat2(r,o,null,e,i):a._stat2(r,o,n,t,i)});a._stat2(r,o,n,e,i)}});c&&u.lstat(o,c)},b.prototype._stat2=function(n,t,e,r,i){if(e)return this.statCache[t]=!1,i();var o="/"===n.slice(-1);if(this.statCache[t]=r,"/"===t.slice(-1)&&!r.isDirectory())return i(null,!1,r);var s=r.isDirectory()?"DIR":"FILE";return this.cache[t]=this.cache[t]||s,o&&"DIR"!==s?i():i(null,s,r)}}).call(this,_("_process"))},{"./common.js":15,"./sync.js":17,_process:24,assert:9,events:14,fs:12,inflight:18,inherits:19,minimatch:20,once:21,path:22,"path-is-absolute":23,util:28}],17:[function(e,r,n){(function(i){(r.exports=n).GlobSync=h;var s=e("fs"),c=e("minimatch"),g=(c.Minimatch,e("./glob.js").Glob,e("util"),e("path")),u=e("assert"),l=e("path-is-absolute"),t=e("./common.js"),o=(t.alphasort,t.alphasorti,t.setopts),a=t.ownProp,f=t.childrenIgnored;function n(n,t){if("function"==typeof t||3===arguments.length)throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167");return new h(n,t).found}function h(n,t){if(!n)throw new Error("must provide pattern");if("function"==typeof t||3===arguments.length)throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167");if(!(this instanceof h))return new h(n,t);if(o(this,n,t),this.noprocess)return this;var e=this.minimatch.set.length;this.matches=new Array(e);for(var r=0;rthis.maxLength)return!1;if(!this.stat&&a(this.cache,t)){var r=this.cache[t];if(Array.isArray(r)&&(r="DIR"),!e||"DIR"===r)return r;if(e&&"FILE"===r)return!1}var i=this.statCache[t];if(!i){var o;try{o=s.lstatSync(t)}catch(n){return!1}if(o.isSymbolicLink())try{i=s.statSync(t)}catch(n){i=o}else i=o}r=(this.statCache[t]=i).isDirectory()?"DIR":"FILE";return this.cache[t]=this.cache[t]||r,(!e||"DIR"===r)&&r},h.prototype._mark=function(n){return t.mark(this,n)},h.prototype._makeAbs=function(n){return t.makeAbs(this,n)}}).call(this,e("_process"))},{"./common.js":15,"./glob.js":16,_process:24,assert:9,fs:12,minimatch:20,path:22,"path-is-absolute":23,util:28}],18:[function(t,r,n){(function(s){var n=t("wrappy"),a=Object.create(null),e=t("once");r.exports=n(function(n,t){return a[n]?(a[n].push(t),null):(a[n]=[t],o=n,e(function n(){var t=a[o],e=t.length,r=function(n){for(var t=n.length,e=[],r=0;re?(t.splice(0,e),s.nextTick(function(){n.apply(null,r)})):delete a[o]}}));var o})}).call(this,t("_process"))},{_process:24,once:21,wrappy:29}],19:[function(n,t,e){"function"==typeof Object.create?t.exports=function(n,t){n.super_=t,n.prototype=Object.create(t.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(n,t){n.super_=t;var e=function(){};e.prototype=t.prototype,n.prototype=new e,n.prototype.constructor=n}},{}],20:[function(n,t,e){(t.exports=s).Minimatch=i;var u={sep:"/"};try{u=n("path")}catch(n){}var M=s.GLOBSTAR=i.GLOBSTAR={},r=n("brace-expansion"),C={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},P="[^/]",z=P+"*?",B="().*{}+?[]^$\\!".split("").reduce(function(n,t){return n[t]=!0,n},{});var l=/\/+/;function o(t,e){t=t||{},e=e||{};var r={};return Object.keys(e).forEach(function(n){r[n]=e[n]}),Object.keys(t).forEach(function(n){r[n]=t[n]}),r}function s(n,t,e){if("string"!=typeof t)throw new TypeError("glob pattern string required");return e||(e={}),!(!e.nocomment&&"#"===t.charAt(0))&&(""===t.trim()?""===n:new i(t,e).match(n))}function i(n,t){if(!(this instanceof i))return new i(n,t);if("string"!=typeof n)throw new TypeError("glob pattern string required");t||(t={}),n=n.trim(),"/"!==u.sep&&(n=n.split(u.sep).join("/")),this.options=t,this.set=[],this.pattern=n,this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.make()}function a(n,t){if(t||(t=this instanceof i?this.options:{}),void 0===(n=void 0===n?this.pattern:n))throw new TypeError("undefined pattern");return t.nobrace||!n.match(/\{.*\}/)?[n]:r(n)}s.filter=function(r,i){return i=i||{},function(n,t,e){return s(n,r,i)}},s.defaults=function(r){if(!r||!Object.keys(r).length)return s;var i=s,n=function(n,t,e){return i.minimatch(n,t,o(r,e))};return n.Minimatch=function(n,t){return new i.Minimatch(n,o(r,t))},n},i.defaults=function(n){return n&&Object.keys(n).length?s.defaults(n).Minimatch:i},i.prototype.debug=function(){},i.prototype.make=function(){if(this._made)return;var n=this.pattern,t=this.options;if(!t.nocomment&&"#"===n.charAt(0))return void(this.comment=!0);if(!n)return void(this.empty=!0);this.parseNegate();var e=this.globSet=this.braceExpand();t.debug&&(this.debug=console.error);this.debug(this.pattern,e),e=this.globParts=e.map(function(n){return n.split(l)}),this.debug(this.pattern,e),e=e.map(function(n,t,e){return n.map(this.parse,this)},this),this.debug(this.pattern,e),e=e.filter(function(n){return-1===n.indexOf(!1)}),this.debug(this.pattern,e),this.set=e},i.prototype.parseNegate=function(){var n=this.pattern,t=!1,e=this.options,r=0;if(e.nonegate)return;for(var i=0,o=n.length;i>> no match, partial?",n,f,t,h),f!==s))}if("string"==typeof u?(c=r.nocase?l.toLowerCase()===u.toLowerCase():l===u,this.debug("string match",u,l,c)):(c=l.match(u),this.debug("pattern match",u,l,c)),!c)return!1}if(i===s&&o===a)return!0;if(i===s)return e;if(o===a)return i===s-1&&""===n[i];throw new Error("wtf?")}},{"brace-expansion":11,path:22}],21:[function(n,t,e){var r=n("wrappy");function i(n){var t=function(){return t.called?t.value:(t.called=!0,t.value=n.apply(this,arguments))};return t.called=!1,t}function o(n){var t=function(){if(t.called)throw new Error(t.onceError);return t.called=!0,t.value=n.apply(this,arguments)},e=n.name||"Function wrapped with `once`";return t.onceError=e+" shouldn't be called more than once",t.called=!1,t}t.exports=r(i),t.exports.strict=r(o),i.proto=i(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return i(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return o(this)},configurable:!0})})},{wrappy:29}],22:[function(n,t,u){(function(i){function o(n,t){for(var e=0,r=n.length-1;0<=r;r--){var i=n[r];"."===i?n.splice(r,1):".."===i?(n.splice(r,1),e++):e&&(n.splice(r,1),e--)}if(t)for(;e--;e)n.unshift("..");return n}var t=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,s=function(n){return t.exec(n).slice(1)};function a(n,t){if(n.filter)return n.filter(t);for(var e=[],r=0;r":">",'"':""","'":"'","`":"`"},D=d.invert(N),F=function(t){var e=function(n){return t[n]},n="(?:"+d.keys(t).join("|")+")",r=RegExp(n),i=RegExp(n,"g");return function(n){return n=null==n?"":""+n,r.test(n)?n.replace(i,e):n}};d.escape=F(N),d.unescape=F(D),d.result=function(n,t,e){var r=null==n?void 0:n[t];return void 0===r&&(r=e),d.isFunction(r)?r.call(n):r};var M=0;d.uniqueId=function(n){var t=++M+"";return n?n+t:t},d.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var C=/(.)^/,P={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\u2028|\u2029/g,B=function(n){return"\\"+P[n]};d.template=function(o,n,t){!n&&t&&(n=t),n=d.defaults({},n,d.templateSettings);var e=RegExp([(n.escape||C).source,(n.interpolate||C).source,(n.evaluate||C).source].join("|")+"|$","g"),s=0,a="__p+='";o.replace(e,function(n,t,e,r,i){return a+=o.slice(s,i).replace(z,B),s=i+n.length,t?a+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?a+="'+\n((__t=("+e+"))==null?'':__t)+\n'":r&&(a+="';\n"+r+"\n__p+='"),n}),a+="';\n",n.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{var r=new Function(n.variable||"obj","_",a)}catch(n){throw n.source=a,n}var i=function(n){return r.call(this,n,d)},c=n.variable||"obj";return i.source="function("+c+"){\n"+a+"}",i},d.chain=function(n){var t=d(n);return t._chain=!0,t};var U=function(n,t){return n._chain?d(t).chain():t};d.mixin=function(e){d.each(d.functions(e),function(n){var t=d[n]=e[n];d.prototype[n]=function(){var n=[this._wrapped];return i.apply(n,arguments),U(this,t.apply(d,n))}})},d.mixin(d),d.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=r[t];d.prototype[t]=function(){var n=this._wrapped;return e.apply(n,arguments),"shift"!==t&&"splice"!==t||0!==n.length||delete n[0],U(this,n)}}),d.each(["concat","join","slice"],function(n){var t=r[n];d.prototype[n]=function(){return U(this,t.apply(this._wrapped,arguments))}}),d.prototype.value=function(){return this._wrapped},d.prototype.valueOf=d.prototype.toJSON=d.prototype.value,d.prototype.toString=function(){return""+this._wrapped}}).call(this)},{}],26:[function(n,t,e){arguments[4][19][0].apply(e,arguments)},{dup:19}],27:[function(n,t,e){t.exports=function(n){return n&&"object"==typeof n&&"function"==typeof n.copy&&"function"==typeof n.fill&&"function"==typeof n.readUInt8}},{}],28:[function(h,n,k){(function(r,i){var a=/%[sdj%]/g;k.format=function(n){if(!_(n)){for(var t=[],e=0;e.md-nav__link{color:inherit}button[data-md-color-primary=pink]{background-color:#e91e63}[data-md-color-primary=pink] .md-typeset a{color:#e91e63}[data-md-color-primary=pink] .md-header,[data-md-color-primary=pink] .md-hero{background-color:#e91e63}[data-md-color-primary=pink] .md-nav__link--active,[data-md-color-primary=pink] .md-nav__link:active{color:#e91e63}[data-md-color-primary=pink] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=purple]{background-color:#ab47bc}[data-md-color-primary=purple] .md-typeset a{color:#ab47bc}[data-md-color-primary=purple] .md-header,[data-md-color-primary=purple] .md-hero{background-color:#ab47bc}[data-md-color-primary=purple] .md-nav__link--active,[data-md-color-primary=purple] .md-nav__link:active{color:#ab47bc}[data-md-color-primary=purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-purple]{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-typeset a{color:#7e57c2}[data-md-color-primary=deep-purple] .md-header,[data-md-color-primary=deep-purple] .md-hero{background-color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__link--active,[data-md-color-primary=deep-purple] .md-nav__link:active{color:#7e57c2}[data-md-color-primary=deep-purple] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=indigo]{background-color:#3f51b5}[data-md-color-primary=indigo] .md-typeset a{color:#3f51b5}[data-md-color-primary=indigo] .md-header,[data-md-color-primary=indigo] .md-hero{background-color:#3f51b5}[data-md-color-primary=indigo] .md-nav__link--active,[data-md-color-primary=indigo] .md-nav__link:active{color:#3f51b5}[data-md-color-primary=indigo] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue]{background-color:#2196f3}[data-md-color-primary=blue] .md-typeset a{color:#2196f3}[data-md-color-primary=blue] .md-header,[data-md-color-primary=blue] .md-hero{background-color:#2196f3}[data-md-color-primary=blue] .md-nav__link--active,[data-md-color-primary=blue] .md-nav__link:active{color:#2196f3}[data-md-color-primary=blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-blue]{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-typeset a{color:#03a9f4}[data-md-color-primary=light-blue] .md-header,[data-md-color-primary=light-blue] .md-hero{background-color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__link--active,[data-md-color-primary=light-blue] .md-nav__link:active{color:#03a9f4}[data-md-color-primary=light-blue] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=cyan]{background-color:#00bcd4}[data-md-color-primary=cyan] .md-typeset a{color:#00bcd4}[data-md-color-primary=cyan] .md-header,[data-md-color-primary=cyan] .md-hero{background-color:#00bcd4}[data-md-color-primary=cyan] .md-nav__link--active,[data-md-color-primary=cyan] .md-nav__link:active{color:#00bcd4}[data-md-color-primary=cyan] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=teal]{background-color:#009688}[data-md-color-primary=teal] .md-typeset a{color:#009688}[data-md-color-primary=teal] .md-header,[data-md-color-primary=teal] .md-hero{background-color:#009688}[data-md-color-primary=teal] .md-nav__link--active,[data-md-color-primary=teal] .md-nav__link:active{color:#009688}[data-md-color-primary=teal] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=green]{background-color:#4caf50}[data-md-color-primary=green] .md-typeset a{color:#4caf50}[data-md-color-primary=green] .md-header,[data-md-color-primary=green] .md-hero{background-color:#4caf50}[data-md-color-primary=green] .md-nav__link--active,[data-md-color-primary=green] .md-nav__link:active{color:#4caf50}[data-md-color-primary=green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=light-green]{background-color:#7cb342}[data-md-color-primary=light-green] .md-typeset a{color:#7cb342}[data-md-color-primary=light-green] .md-header,[data-md-color-primary=light-green] .md-hero{background-color:#7cb342}[data-md-color-primary=light-green] .md-nav__link--active,[data-md-color-primary=light-green] .md-nav__link:active{color:#7cb342}[data-md-color-primary=light-green] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=lime]{background-color:#c0ca33}[data-md-color-primary=lime] .md-typeset a{color:#c0ca33}[data-md-color-primary=lime] .md-header,[data-md-color-primary=lime] .md-hero{background-color:#c0ca33}[data-md-color-primary=lime] .md-nav__link--active,[data-md-color-primary=lime] .md-nav__link:active{color:#c0ca33}[data-md-color-primary=lime] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=yellow]{background-color:#f9a825}[data-md-color-primary=yellow] .md-typeset a{color:#f9a825}[data-md-color-primary=yellow] .md-header,[data-md-color-primary=yellow] .md-hero{background-color:#f9a825}[data-md-color-primary=yellow] .md-nav__link--active,[data-md-color-primary=yellow] .md-nav__link:active{color:#f9a825}[data-md-color-primary=yellow] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=amber]{background-color:#ffa000}[data-md-color-primary=amber] .md-typeset a{color:#ffa000}[data-md-color-primary=amber] .md-header,[data-md-color-primary=amber] .md-hero{background-color:#ffa000}[data-md-color-primary=amber] .md-nav__link--active,[data-md-color-primary=amber] .md-nav__link:active{color:#ffa000}[data-md-color-primary=amber] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=orange]{background-color:#fb8c00}[data-md-color-primary=orange] .md-typeset a{color:#fb8c00}[data-md-color-primary=orange] .md-header,[data-md-color-primary=orange] .md-hero{background-color:#fb8c00}[data-md-color-primary=orange] .md-nav__link--active,[data-md-color-primary=orange] .md-nav__link:active{color:#fb8c00}[data-md-color-primary=orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=deep-orange]{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-typeset a{color:#ff7043}[data-md-color-primary=deep-orange] .md-header,[data-md-color-primary=deep-orange] .md-hero{background-color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__link--active,[data-md-color-primary=deep-orange] .md-nav__link:active{color:#ff7043}[data-md-color-primary=deep-orange] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=brown]{background-color:#795548}[data-md-color-primary=brown] .md-typeset a{color:#795548}[data-md-color-primary=brown] .md-header,[data-md-color-primary=brown] .md-hero{background-color:#795548}[data-md-color-primary=brown] .md-nav__link--active,[data-md-color-primary=brown] .md-nav__link:active{color:#795548}[data-md-color-primary=brown] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=grey]{background-color:#757575}[data-md-color-primary=grey] .md-typeset a{color:#757575}[data-md-color-primary=grey] .md-header,[data-md-color-primary=grey] .md-hero{background-color:#757575}[data-md-color-primary=grey] .md-nav__link--active,[data-md-color-primary=grey] .md-nav__link:active{color:#757575}[data-md-color-primary=grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=blue-grey]{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-typeset a{color:#546e7a}[data-md-color-primary=blue-grey] .md-header,[data-md-color-primary=blue-grey] .md-hero{background-color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__link--active,[data-md-color-primary=blue-grey] .md-nav__link:active{color:#546e7a}[data-md-color-primary=blue-grey] .md-nav__item--nested>.md-nav__link{color:inherit}button[data-md-color-primary=white]{box-shadow:inset 0 0 .05rem rgba(0,0,0,.54)}[data-md-color-primary=white] .md-header,[data-md-color-primary=white] .md-hero,button[data-md-color-primary=white]{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero--expand{border-bottom:.05rem solid rgba(0,0,0,.07)}[data-md-color-primary=black] .md-header,[data-md-color-primary=black] .md-hero,button[data-md-color-primary=black]{background-color:#000}button[data-md-color-accent=red]{background-color:#ff1744}[data-md-color-accent=red] .md-typeset a:active,[data-md-color-accent=red] .md-typeset a:hover{color:#ff1744}[data-md-color-accent=red] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=red] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-nav__link:focus,[data-md-color-accent=red] .md-nav__link:hover,[data-md-color-accent=red] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=red] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=red] .md-typeset .md-clipboard:active:before,[data-md-color-accent=red] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=red] .md-typeset [id] .headerlink:focus,[data-md-color-accent=red] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=red] .md-typeset [id]:target .headerlink{color:#ff1744}[data-md-color-accent=red] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-search-result__link:hover,[data-md-color-accent=red] .md-search-result__link[data-md-state=active]{background-color:rgba(255,23,68,.1)}[data-md-color-accent=red] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff1744}[data-md-color-accent=red] .md-source-file:hover:before{background-color:#ff1744}button[data-md-color-accent=pink]{background-color:#f50057}[data-md-color-accent=pink] .md-typeset a:active,[data-md-color-accent=pink] .md-typeset a:hover{color:#f50057}[data-md-color-accent=pink] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=pink] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-nav__link:focus,[data-md-color-accent=pink] .md-nav__link:hover,[data-md-color-accent=pink] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=pink] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=pink] .md-typeset .md-clipboard:active:before,[data-md-color-accent=pink] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=pink] .md-typeset [id] .headerlink:focus,[data-md-color-accent=pink] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=pink] .md-typeset [id]:target .headerlink{color:#f50057}[data-md-color-accent=pink] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-search-result__link:hover,[data-md-color-accent=pink] .md-search-result__link[data-md-state=active]{background-color:rgba(245,0,87,.1)}[data-md-color-accent=pink] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#f50057}[data-md-color-accent=pink] .md-source-file:hover:before{background-color:#f50057}button[data-md-color-accent=purple]{background-color:#e040fb}[data-md-color-accent=purple] .md-typeset a:active,[data-md-color-accent=purple] .md-typeset a:hover{color:#e040fb}[data-md-color-accent=purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-nav__link:focus,[data-md-color-accent=purple] .md-nav__link:hover,[data-md-color-accent=purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=purple] .md-typeset [id]:target .headerlink{color:#e040fb}[data-md-color-accent=purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-search-result__link:hover,[data-md-color-accent=purple] .md-search-result__link[data-md-state=active]{background-color:rgba(224,64,251,.1)}[data-md-color-accent=purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#e040fb}[data-md-color-accent=purple] .md-source-file:hover:before{background-color:#e040fb}button[data-md-color-accent=deep-purple]{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset a:active,[data-md-color-accent=deep-purple] .md-typeset a:hover{color:#7c4dff}[data-md-color-accent=deep-purple] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-purple] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-nav__link:focus,[data-md-color-accent=deep-purple] .md-nav__link:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-purple] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-purple] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-purple] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-purple] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-purple] .md-typeset [id]:target .headerlink{color:#7c4dff}[data-md-color-accent=deep-purple] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-search-result__link:hover,[data-md-color-accent=deep-purple] .md-search-result__link[data-md-state=active]{background-color:rgba(124,77,255,.1)}[data-md-color-accent=deep-purple] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#7c4dff}[data-md-color-accent=deep-purple] .md-source-file:hover:before{background-color:#7c4dff}button[data-md-color-accent=indigo]{background-color:#536dfe}[data-md-color-accent=indigo] .md-typeset a:active,[data-md-color-accent=indigo] .md-typeset a:hover{color:#536dfe}[data-md-color-accent=indigo] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=indigo] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-nav__link:focus,[data-md-color-accent=indigo] .md-nav__link:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=indigo] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=indigo] .md-typeset .md-clipboard:active:before,[data-md-color-accent=indigo] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=indigo] .md-typeset [id] .headerlink:focus,[data-md-color-accent=indigo] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=indigo] .md-typeset [id]:target .headerlink{color:#536dfe}[data-md-color-accent=indigo] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-search-result__link:hover,[data-md-color-accent=indigo] .md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}[data-md-color-accent=indigo] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}[data-md-color-accent=indigo] .md-source-file:hover:before{background-color:#536dfe}button[data-md-color-accent=blue]{background-color:#448aff}[data-md-color-accent=blue] .md-typeset a:active,[data-md-color-accent=blue] .md-typeset a:hover{color:#448aff}[data-md-color-accent=blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-nav__link:focus,[data-md-color-accent=blue] .md-nav__link:hover,[data-md-color-accent=blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=blue] .md-typeset [id]:target .headerlink{color:#448aff}[data-md-color-accent=blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-search-result__link:hover,[data-md-color-accent=blue] .md-search-result__link[data-md-state=active]{background-color:rgba(68,138,255,.1)}[data-md-color-accent=blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#448aff}[data-md-color-accent=blue] .md-source-file:hover:before{background-color:#448aff}button[data-md-color-accent=light-blue]{background-color:#0091ea}[data-md-color-accent=light-blue] .md-typeset a:active,[data-md-color-accent=light-blue] .md-typeset a:hover{color:#0091ea}[data-md-color-accent=light-blue] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-blue] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-nav__link:focus,[data-md-color-accent=light-blue] .md-nav__link:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-blue] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-blue] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-blue] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-blue] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-blue] .md-typeset [id]:target .headerlink{color:#0091ea}[data-md-color-accent=light-blue] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-search-result__link:hover,[data-md-color-accent=light-blue] .md-search-result__link[data-md-state=active]{background-color:rgba(0,145,234,.1)}[data-md-color-accent=light-blue] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#0091ea}[data-md-color-accent=light-blue] .md-source-file:hover:before{background-color:#0091ea}button[data-md-color-accent=cyan]{background-color:#00b8d4}[data-md-color-accent=cyan] .md-typeset a:active,[data-md-color-accent=cyan] .md-typeset a:hover{color:#00b8d4}[data-md-color-accent=cyan] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=cyan] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-nav__link:focus,[data-md-color-accent=cyan] .md-nav__link:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=cyan] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=cyan] .md-typeset .md-clipboard:active:before,[data-md-color-accent=cyan] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=cyan] .md-typeset [id] .headerlink:focus,[data-md-color-accent=cyan] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=cyan] .md-typeset [id]:target .headerlink{color:#00b8d4}[data-md-color-accent=cyan] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-search-result__link:hover,[data-md-color-accent=cyan] .md-search-result__link[data-md-state=active]{background-color:rgba(0,184,212,.1)}[data-md-color-accent=cyan] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00b8d4}[data-md-color-accent=cyan] .md-source-file:hover:before{background-color:#00b8d4}button[data-md-color-accent=teal]{background-color:#00bfa5}[data-md-color-accent=teal] .md-typeset a:active,[data-md-color-accent=teal] .md-typeset a:hover{color:#00bfa5}[data-md-color-accent=teal] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=teal] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-nav__link:focus,[data-md-color-accent=teal] .md-nav__link:hover,[data-md-color-accent=teal] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=teal] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=teal] .md-typeset .md-clipboard:active:before,[data-md-color-accent=teal] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=teal] .md-typeset [id] .headerlink:focus,[data-md-color-accent=teal] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=teal] .md-typeset [id]:target .headerlink{color:#00bfa5}[data-md-color-accent=teal] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-search-result__link:hover,[data-md-color-accent=teal] .md-search-result__link[data-md-state=active]{background-color:rgba(0,191,165,.1)}[data-md-color-accent=teal] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00bfa5}[data-md-color-accent=teal] .md-source-file:hover:before{background-color:#00bfa5}button[data-md-color-accent=green]{background-color:#00c853}[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#00c853}[data-md-color-accent=green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-nav__link:focus,[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#00c853}[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-search-result__link:hover,[data-md-color-accent=green] .md-search-result__link[data-md-state=active]{background-color:rgba(0,200,83,.1)}[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#00c853}[data-md-color-accent=green] .md-source-file:hover:before{background-color:#00c853}button[data-md-color-accent=light-green]{background-color:#64dd17}[data-md-color-accent=light-green] .md-typeset a:active,[data-md-color-accent=light-green] .md-typeset a:hover{color:#64dd17}[data-md-color-accent=light-green] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=light-green] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-nav__link:focus,[data-md-color-accent=light-green] .md-nav__link:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=light-green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=light-green] .md-typeset .md-clipboard:active:before,[data-md-color-accent=light-green] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=light-green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=light-green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=light-green] .md-typeset [id]:target .headerlink{color:#64dd17}[data-md-color-accent=light-green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-search-result__link:hover,[data-md-color-accent=light-green] .md-search-result__link[data-md-state=active]{background-color:rgba(100,221,23,.1)}[data-md-color-accent=light-green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#64dd17}[data-md-color-accent=light-green] .md-source-file:hover:before{background-color:#64dd17}button[data-md-color-accent=lime]{background-color:#aeea00}[data-md-color-accent=lime] .md-typeset a:active,[data-md-color-accent=lime] .md-typeset a:hover{color:#aeea00}[data-md-color-accent=lime] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=lime] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-nav__link:focus,[data-md-color-accent=lime] .md-nav__link:hover,[data-md-color-accent=lime] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=lime] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=lime] .md-typeset .md-clipboard:active:before,[data-md-color-accent=lime] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=lime] .md-typeset [id] .headerlink:focus,[data-md-color-accent=lime] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=lime] .md-typeset [id]:target .headerlink{color:#aeea00}[data-md-color-accent=lime] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-search-result__link:hover,[data-md-color-accent=lime] .md-search-result__link[data-md-state=active]{background-color:rgba(174,234,0,.1)}[data-md-color-accent=lime] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#aeea00}[data-md-color-accent=lime] .md-source-file:hover:before{background-color:#aeea00}button[data-md-color-accent=yellow]{background-color:#ffd600}[data-md-color-accent=yellow] .md-typeset a:active,[data-md-color-accent=yellow] .md-typeset a:hover{color:#ffd600}[data-md-color-accent=yellow] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=yellow] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-nav__link:focus,[data-md-color-accent=yellow] .md-nav__link:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=yellow] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=yellow] .md-typeset .md-clipboard:active:before,[data-md-color-accent=yellow] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=yellow] .md-typeset [id] .headerlink:focus,[data-md-color-accent=yellow] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=yellow] .md-typeset [id]:target .headerlink{color:#ffd600}[data-md-color-accent=yellow] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-search-result__link:hover,[data-md-color-accent=yellow] .md-search-result__link[data-md-state=active]{background-color:rgba(255,214,0,.1)}[data-md-color-accent=yellow] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffd600}[data-md-color-accent=yellow] .md-source-file:hover:before{background-color:#ffd600}button[data-md-color-accent=amber]{background-color:#ffab00}[data-md-color-accent=amber] .md-typeset a:active,[data-md-color-accent=amber] .md-typeset a:hover{color:#ffab00}[data-md-color-accent=amber] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=amber] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-nav__link:focus,[data-md-color-accent=amber] .md-nav__link:hover,[data-md-color-accent=amber] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=amber] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=amber] .md-typeset .md-clipboard:active:before,[data-md-color-accent=amber] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=amber] .md-typeset [id] .headerlink:focus,[data-md-color-accent=amber] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=amber] .md-typeset [id]:target .headerlink{color:#ffab00}[data-md-color-accent=amber] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-search-result__link:hover,[data-md-color-accent=amber] .md-search-result__link[data-md-state=active]{background-color:rgba(255,171,0,.1)}[data-md-color-accent=amber] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ffab00}[data-md-color-accent=amber] .md-source-file:hover:before{background-color:#ffab00}button[data-md-color-accent=orange]{background-color:#ff9100}[data-md-color-accent=orange] .md-typeset a:active,[data-md-color-accent=orange] .md-typeset a:hover{color:#ff9100}[data-md-color-accent=orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-nav__link:focus,[data-md-color-accent=orange] .md-nav__link:hover,[data-md-color-accent=orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=orange] .md-typeset [id]:target .headerlink{color:#ff9100}[data-md-color-accent=orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-search-result__link:hover,[data-md-color-accent=orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,145,0,.1)}[data-md-color-accent=orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff9100}[data-md-color-accent=orange] .md-source-file:hover:before{background-color:#ff9100}button[data-md-color-accent=deep-orange]{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset a:active,[data-md-color-accent=deep-orange] .md-typeset a:hover{color:#ff6e40}[data-md-color-accent=deep-orange] .md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,[data-md-color-accent=deep-orange] .md-typeset pre code::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-nav__link:focus,[data-md-color-accent=deep-orange] .md-nav__link:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=deep-orange] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:active:before,[data-md-color-accent=deep-orange] .md-typeset .md-clipboard:hover:before,[data-md-color-accent=deep-orange] .md-typeset [id] .headerlink:focus,[data-md-color-accent=deep-orange] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=deep-orange] .md-typeset [id]:target .headerlink{color:#ff6e40}[data-md-color-accent=deep-orange] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-search-result__link:hover,[data-md-color-accent=deep-orange] .md-search-result__link[data-md-state=active]{background-color:rgba(255,110,64,.1)}[data-md-color-accent=deep-orange] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#ff6e40}[data-md-color-accent=deep-orange] .md-source-file:hover:before{background-color:#ff6e40}@media only screen and (max-width:59.9375em){[data-md-color-primary=red] .md-nav__source{background-color:rgba(190,66,64,.9675)}[data-md-color-primary=pink] .md-nav__source{background-color:rgba(185,24,79,.9675)}[data-md-color-primary=purple] .md-nav__source{background-color:rgba(136,57,150,.9675)}[data-md-color-primary=deep-purple] .md-nav__source{background-color:rgba(100,69,154,.9675)}[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(50,64,144,.9675)}[data-md-color-primary=blue] .md-nav__source{background-color:rgba(26,119,193,.9675)}[data-md-color-primary=light-blue] .md-nav__source{background-color:rgba(2,134,194,.9675)}[data-md-color-primary=cyan] .md-nav__source{background-color:rgba(0,150,169,.9675)}[data-md-color-primary=teal] .md-nav__source{background-color:rgba(0,119,108,.9675)}[data-md-color-primary=green] .md-nav__source{background-color:rgba(60,139,64,.9675)}[data-md-color-primary=light-green] .md-nav__source{background-color:rgba(99,142,53,.9675)}[data-md-color-primary=lime] .md-nav__source{background-color:rgba(153,161,41,.9675)}[data-md-color-primary=yellow] .md-nav__source{background-color:rgba(198,134,29,.9675)}[data-md-color-primary=amber] .md-nav__source{background-color:rgba(203,127,0,.9675)}[data-md-color-primary=orange] .md-nav__source{background-color:rgba(200,111,0,.9675)}[data-md-color-primary=deep-orange] .md-nav__source{background-color:rgba(203,89,53,.9675)}[data-md-color-primary=brown] .md-nav__source{background-color:rgba(96,68,57,.9675)}[data-md-color-primary=grey] .md-nav__source{background-color:rgba(93,93,93,.9675)}[data-md-color-primary=blue-grey] .md-nav__source{background-color:rgba(67,88,97,.9675)}[data-md-color-primary=white] .md-nav__source{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-nav__source{background-color:#404040}}@media only screen and (max-width:76.1875em){html [data-md-color-primary=red] .md-nav--primary .md-nav__title--site{background-color:#ef5350}html [data-md-color-primary=pink] .md-nav--primary .md-nav__title--site{background-color:#e91e63}html [data-md-color-primary=purple] .md-nav--primary .md-nav__title--site{background-color:#ab47bc}html [data-md-color-primary=deep-purple] .md-nav--primary .md-nav__title--site{background-color:#7e57c2}html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3f51b5}html [data-md-color-primary=blue] .md-nav--primary .md-nav__title--site{background-color:#2196f3}html [data-md-color-primary=light-blue] .md-nav--primary .md-nav__title--site{background-color:#03a9f4}html [data-md-color-primary=cyan] .md-nav--primary .md-nav__title--site{background-color:#00bcd4}html [data-md-color-primary=teal] .md-nav--primary .md-nav__title--site{background-color:#009688}html [data-md-color-primary=green] .md-nav--primary .md-nav__title--site{background-color:#4caf50}html [data-md-color-primary=light-green] .md-nav--primary .md-nav__title--site{background-color:#7cb342}html [data-md-color-primary=lime] .md-nav--primary .md-nav__title--site{background-color:#c0ca33}html [data-md-color-primary=yellow] .md-nav--primary .md-nav__title--site{background-color:#f9a825}html [data-md-color-primary=amber] .md-nav--primary .md-nav__title--site{background-color:#ffa000}html [data-md-color-primary=orange] .md-nav--primary .md-nav__title--site{background-color:#fb8c00}html [data-md-color-primary=deep-orange] .md-nav--primary .md-nav__title--site{background-color:#ff7043}html [data-md-color-primary=brown] .md-nav--primary .md-nav__title--site{background-color:#795548}html [data-md-color-primary=grey] .md-nav--primary .md-nav__title--site{background-color:#757575}html [data-md-color-primary=blue-grey] .md-nav--primary .md-nav__title--site{background-color:#546e7a}html [data-md-color-primary=white] .md-nav--primary .md-nav__title--site{background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=white] .md-hero{border-bottom:.05rem solid rgba(0,0,0,.07)}html [data-md-color-primary=black] .md-nav--primary .md-nav__title--site{background-color:#000}}@media only screen and (min-width:76.25em){[data-md-color-primary=red] .md-tabs{background-color:#ef5350}[data-md-color-primary=pink] .md-tabs{background-color:#e91e63}[data-md-color-primary=purple] .md-tabs{background-color:#ab47bc}[data-md-color-primary=deep-purple] .md-tabs{background-color:#7e57c2}[data-md-color-primary=indigo] .md-tabs{background-color:#3f51b5}[data-md-color-primary=blue] .md-tabs{background-color:#2196f3}[data-md-color-primary=light-blue] .md-tabs{background-color:#03a9f4}[data-md-color-primary=cyan] .md-tabs{background-color:#00bcd4}[data-md-color-primary=teal] .md-tabs{background-color:#009688}[data-md-color-primary=green] .md-tabs{background-color:#4caf50}[data-md-color-primary=light-green] .md-tabs{background-color:#7cb342}[data-md-color-primary=lime] .md-tabs{background-color:#c0ca33}[data-md-color-primary=yellow] .md-tabs{background-color:#f9a825}[data-md-color-primary=amber] .md-tabs{background-color:#ffa000}[data-md-color-primary=orange] .md-tabs{background-color:#fb8c00}[data-md-color-primary=deep-orange] .md-tabs{background-color:#ff7043}[data-md-color-primary=brown] .md-tabs{background-color:#795548}[data-md-color-primary=grey] .md-tabs{background-color:#757575}[data-md-color-primary=blue-grey] .md-tabs{background-color:#546e7a}[data-md-color-primary=white] .md-tabs{border-bottom:.05rem solid rgba(0,0,0,.07);background-color:#fff;color:rgba(0,0,0,.87)}[data-md-color-primary=black] .md-tabs{background-color:#000}}@media only screen and (min-width:60em){[data-md-color-primary=white] .md-search__input{background-color:rgba(0,0,0,.07)}[data-md-color-primary=white] .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=white] .md-search__input::placeholder{color:rgba(0,0,0,.54)}[data-md-color-primary=black] .md-search__input{background-color:hsla(0,0%,100%,.3)}} \ No newline at end of file diff --git a/assets/stylesheets/application.30686662.css b/assets/stylesheets/application.30686662.css new file mode 100644 index 00000000..77e8a7e7 --- /dev/null +++ b/assets/stylesheets/application.30686662.css @@ -0,0 +1 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;-ms-text-size-adjust:none;text-size-adjust:none}body{margin:0}hr{overflow:visible;box-sizing:content-box}a{-webkit-text-decoration-skip:objects}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}small,sub,sup{font-size:80%}sub,sup{position:relative;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}table{border-collapse:separate;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{margin:0;padding:0;border:0;outline-style:none;background:transparent;font-size:inherit}input{border:0;outline:0}.md-clipboard:before,.md-icon,.md-nav__button,.md-nav__link:after,.md-nav__title:before,.md-search-result__article--document:before,.md-source-file:before,.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset .critic.comment:before,.md-typeset .footnote-backref,.md-typeset .task-list-control .task-list-indicator:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before,.md-typeset summary:after{font-family:Material Icons;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-content__icon,.md-footer-nav__button,.md-header-nav__button,.md-nav__button,.md-nav__title:before,.md-search-result__article--document:before{display:inline-block;margin:.2rem;padding:.4rem;font-size:1.2rem;cursor:pointer}.md-icon--arrow-back:before{content:""}.md-icon--arrow-forward:before{content:""}.md-icon--menu:before{content:""}.md-icon--search:before{content:""}[dir=rtl] .md-icon--arrow-back:before{content:""}[dir=rtl] .md-icon--arrow-forward:before{content:""}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,input{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern","liga";font-feature-settings:"kern","liga";font-family:Helvetica Neue,Helvetica,Arial,sans-serif}code,kbd,pre{color:rgba(0,0,0,.87);-webkit-font-feature-settings:"kern";font-feature-settings:"kern";font-family:Courier New,Courier,monospace}.md-typeset{font-size:.8rem;line-height:1.6;-webkit-print-color-adjust:exact}.md-typeset blockquote,.md-typeset ol,.md-typeset p,.md-typeset ul{margin:1em 0}.md-typeset h1{margin:0 0 2rem;color:rgba(0,0,0,.54);font-size:1.5625rem;line-height:1.3}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{margin:2rem 0 .8rem;font-size:1.25rem;line-height:1.4}.md-typeset h3{margin:1.6rem 0 .8rem;font-size:1rem;font-weight:400;letter-spacing:-.01em;line-height:1.5}.md-typeset h2+h3{margin-top:.8rem}.md-typeset h4{font-size:.8rem}.md-typeset h4,.md-typeset h5,.md-typeset h6{margin:.8rem 0;font-weight:700;letter-spacing:-.01em}.md-typeset h5,.md-typeset h6{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset h5{text-transform:uppercase}.md-typeset hr{margin:1.5em 0;border-bottom:.05rem dotted rgba(0,0,0,.26)}.md-typeset a{color:#3f51b5;word-break:break-word}.md-typeset a,.md-typeset a:before{-webkit-transition:color .125s;transition:color .125s}.md-typeset a:active,.md-typeset a:hover{color:#536dfe}.md-typeset code,.md-typeset pre{background-color:hsla(0,0%,92.5%,.5);color:#37474f;font-size:85%;direction:ltr}.md-typeset code{margin:0 .29412em;padding:.07353em 0;border-radius:.1rem;box-shadow:.29412em 0 0 hsla(0,0%,92.5%,.5),-.29412em 0 0 hsla(0,0%,92.5%,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset h1 code,.md-typeset h2 code,.md-typeset h3 code,.md-typeset h4 code,.md-typeset h5 code,.md-typeset h6 code{margin:0;background-color:transparent;box-shadow:none}.md-typeset a>code{margin:inherit;padding:inherit;border-radius:initial;background-color:inherit;color:inherit;box-shadow:none}.md-typeset pre{position:relative;margin:1em 0;border-radius:.1rem;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset pre>code{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;font-size:inherit;box-shadow:none;-webkit-box-decoration-break:slice;box-decoration-break:slice;overflow:auto}.md-typeset pre>code::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset kbd{padding:0 .29412em;border-radius:.15rem;border:.05rem solid #c9c9c9;border-bottom-color:#bcbcbc;background-color:#fcfcfc;color:#555;font-size:85%;box-shadow:0 .05rem 0 #b0b0b0;word-break:break-word}.md-typeset mark{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;background-color:rgba(255,235,59,.5);box-shadow:.25em 0 0 rgba(255,235,59,.5),-.25em 0 0 rgba(255,235,59,.5);word-break:break-word;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset abbr{border-bottom:.05rem dotted rgba(0,0,0,.54);text-decoration:none;cursor:help}.md-typeset small{opacity:.75}.md-typeset sub,.md-typeset sup{margin-left:.07812em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.07812em;margin-left:0}.md-typeset blockquote{padding-left:.6rem;border-left:.2rem solid rgba(0,0,0,.26);color:rgba(0,0,0,.54)}[dir=rtl] .md-typeset blockquote{padding-right:.6rem;padding-left:0;border-right:.2rem solid rgba(0,0,0,.26);border-left:initial}.md-typeset ul{list-style-type:disc}.md-typeset ol,.md-typeset ul{margin-left:.625em;padding:0}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em;margin-left:0}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em;margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em;margin-left:0}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin:.5em 0 .5em .625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em;margin-left:0}.md-typeset dd{margin:1em 0 1em 1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em;margin-left:0}.md-typeset iframe,.md-typeset img,.md-typeset svg{max-width:100%}.md-typeset table:not([class]){box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);display:inline-block;max-width:100%;border-radius:.1rem;font-size:.64rem;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{min-width:5rem;padding:.6rem .8rem;background-color:rgba(0,0,0,.54);color:#fff;vertical-align:top}.md-typeset table:not([class]) td{padding:.6rem .8rem;border-top:.05rem solid rgba(0,0,0,.07);vertical-align:top}.md-typeset table:not([class]) tr{-webkit-transition:background-color .125s;transition:background-color .125s}.md-typeset table:not([class]) tr:hover{background-color:rgba(0,0,0,.035);box-shadow:inset 0 .05rem 0 #fff}.md-typeset table:not([class]) tr:first-child td{border-top:0}.md-typeset table:not([class]) a{word-break:normal}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;-webkit-overflow-scrolling:touch}.md-typeset .md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}.md-typeset .md-typeset__table table{display:table;width:100%;margin:0;overflow:hidden}html{font-size:125%;overflow-x:hidden}body,html{height:100%}body{position:relative;font-size:.5rem}hr{display:block;height:.05rem;padding:0;border:0}.md-svg{display:none}.md-grid{max-width:61rem;margin-right:auto;margin-left:auto}.md-container,.md-main{overflow:auto}.md-container{display:table;width:100%;height:100%;padding-top:2.4rem;table-layout:fixed}.md-main{display:table-row;height:100%}.md-main__inner{height:100%;padding-top:1.5rem;padding-bottom:.05rem}.md-toggle{display:none}.md-overlay{position:fixed;top:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);opacity:0;z-index:3}.md-flex{display:table}.md-flex__cell{display:table-cell;position:relative;vertical-align:top}.md-flex__cell--shrink{width:0}.md-flex__cell--stretch{display:table;width:100%;table-layout:fixed}.md-flex__ellipsis{display:table-cell;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-skip{position:fixed;width:.05rem;height:.05rem;margin:.5rem;padding:.3rem .5rem;-webkit-transform:translateY(.4rem);transform:translateY(.4rem);border-radius:.1rem;background-color:rgba(0,0,0,.87);color:#fff;font-size:.64rem;opacity:0;overflow:hidden}.md-skip:focus{width:auto;height:auto;clip:auto;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;z-index:10}@page{margin:25mm}.md-clipboard{position:absolute;top:.3rem;right:.3rem;width:1.4rem;height:1.4rem;border-radius:.1rem;font-size:.8rem;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-clipboard:before{-webkit-transition:color .25s,opacity .25s;transition:color .25s,opacity .25s;color:rgba(0,0,0,.07);content:"\E14D"}.codehilite:hover .md-clipboard:before,.md-typeset .highlight:hover .md-clipboard:before,pre:hover .md-clipboard:before{color:rgba(0,0,0,.54)}.md-clipboard:focus:before,.md-clipboard:hover:before{color:#536dfe}.md-clipboard__message{display:block;position:absolute;top:0;right:1.7rem;padding:.3rem .5rem;-webkit-transform:translateX(.4rem);transform:translateX(.4rem);-webkit-transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s;transition:transform .25s cubic-bezier(.9,.1,.9,0),opacity .175s,-webkit-transform .25s cubic-bezier(.9,.1,.9,0);border-radius:.1rem;background-color:rgba(0,0,0,.54);color:#fff;font-size:.64rem;white-space:nowrap;opacity:0;pointer-events:none}.md-clipboard__message--active{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .175s 75ms,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1;pointer-events:auto}.md-clipboard__message:before{content:attr(aria-label)}.md-clipboard__message:after{display:block;position:absolute;top:50%;right:-.2rem;width:0;margin-top:-.2rem;border-color:transparent rgba(0,0,0,.54);border-style:solid;border-width:.2rem 0 .2rem .2rem;content:""}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}.md-content__inner:before{display:block;height:.4rem;content:""}.md-content__inner>:last-child{margin-bottom:0}.md-content__icon{position:relative;margin:.4rem 0;padding:0;float:right}.md-typeset .md-content__icon{color:rgba(0,0,0,.26)}.md-header{position:fixed;top:0;right:0;left:0;height:2.4rem;-webkit-transition:background-color .25s,color .25s;transition:background-color .25s,color .25s;background-color:#3f51b5;color:#fff;box-shadow:none;z-index:2;-webkit-backface-visibility:hidden;backface-visibility:hidden}.no-js .md-header{-webkit-transition:none;transition:none;box-shadow:none}.md-header[data-md-state=shadow]{-webkit-transition:background-color .25s,color .25s,box-shadow .25s;transition:background-color .25s,color .25s,box-shadow .25s;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.md-header-nav{padding:0 .2rem}.md-header-nav__button{position:relative;-webkit-transition:opacity .25s;transition:opacity .25s;z-index:1}.md-header-nav__button:hover{opacity:.7}.md-header-nav__button.md-logo *{display:block}.no-js .md-header-nav__button.md-icon--search{display:none}.md-header-nav__topic{display:block;position:absolute;-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem)}.no-js .md-header-nav__topic{position:static}.no-js .md-header-nav__topic+.md-header-nav__topic{display:none}.md-header-nav__title{padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(-1.25rem);transform:translateX(-1.25rem);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s,-webkit-transform .4s cubic-bezier(1,.7,.1,.1);opacity:0;z-index:-1;pointer-events:none}[dir=rtl] .md-header-nav__title[data-md-state=active] .md-header-nav__topic{-webkit-transform:translateX(1.25rem);transform:translateX(1.25rem)}.md-header-nav__title[data-md-state=active] .md-header-nav__topic+.md-header-nav__topic{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);opacity:1;z-index:0;pointer-events:auto}.md-header-nav__source{display:none}.md-hero{-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;font-size:1rem;overflow:hidden}.md-hero__inner{margin-top:1rem;padding:.8rem .8rem .4rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);-webkit-transition-delay:.1s;transition-delay:.1s}[data-md-state=hidden] .md-hero__inner{pointer-events:none;-webkit-transform:translateY(.625rem);transform:translateY(.625rem);-webkit-transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:opacity .1s 0s,-webkit-transform 0s .4s;transition:transform 0s .4s,opacity .1s 0s;transition:transform 0s .4s,opacity .1s 0s,-webkit-transform 0s .4s;opacity:0}.md-hero--expand .md-hero__inner{margin-bottom:1.2rem}.md-footer-nav{background-color:rgba(0,0,0,.87);color:#fff}.md-footer-nav__inner{padding:.2rem;overflow:auto}.md-footer-nav__link{padding-top:1.4rem;padding-bottom:.4rem;-webkit-transition:opacity .25s;transition:opacity .25s}.md-footer-nav__link:hover{opacity:.7}.md-footer-nav__link--prev{width:25%;float:left}[dir=rtl] .md-footer-nav__link--prev{float:right}.md-footer-nav__link--next{width:75%;float:right;text-align:right}[dir=rtl] .md-footer-nav__link--next{float:left;text-align:left}.md-footer-nav__button{-webkit-transition:background .25s;transition:background .25s}.md-footer-nav__title{position:relative;padding:0 1rem;font-size:.9rem;line-height:2.4rem}.md-footer-nav__direction{position:absolute;right:0;left:0;margin-top:-1rem;padding:0 1rem;color:hsla(0,0%,100%,.7);font-size:.75rem}.md-footer-meta{background-color:rgba(0,0,0,.895)}.md-footer-meta__inner{padding:.2rem;overflow:auto}html .md-footer-meta.md-typeset a{color:hsla(0,0%,100%,.7)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:#fff}.md-footer-copyright{margin:0 .6rem;padding:.4rem 0;color:hsla(0,0%,100%,.3);font-size:.64rem}.md-footer-copyright__highlight{color:hsla(0,0%,100%,.7)}.md-footer-social{margin:0 .4rem;padding:.2rem 0 .6rem}.md-footer-social__link{display:inline-block;width:1.6rem;height:1.6rem;font-size:.8rem;text-align:center}.md-footer-social__link:before{line-height:1.9}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{display:block;padding:0 .6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden}.md-nav__title:before{display:none;content:"\E5C4"}[dir=rtl] .md-nav__title:before{content:"\E5C8"}.md-nav__title .md-nav__button{display:none}.md-nav__list{margin:0;padding:0;list-style:none}.md-nav__item{padding:0 .6rem}.md-nav__item:last-child{padding-bottom:.6rem}.md-nav__item .md-nav__item{padding-right:0}[dir=rtl] .md-nav__item .md-nav__item{padding-right:.6rem;padding-left:0}.md-nav__item .md-nav__item:last-child{padding-bottom:0}.md-nav__button img{width:100%;height:auto}.md-nav__link{display:block;margin-top:.625em;-webkit-transition:color .125s;transition:color .125s;text-overflow:ellipsis;cursor:pointer;overflow:hidden}.md-nav__item--nested>.md-nav__link:after{content:"\E313"}html .md-nav__link[for=__toc],html .md-nav__link[for=__toc]+.md-nav__link:after,html .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__link[data-md-state=blur]{color:rgba(0,0,0,.54)}.md-nav__link--active,.md-nav__link:active{color:#3f51b5}.md-nav__item--nested>.md-nav__link{color:inherit}.md-nav__link:focus,.md-nav__link:hover{color:#536dfe}.md-nav__source,.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}.md-search__form{position:relative}.md-search__input{position:relative;padding:0 2.2rem 0 3.6rem;text-overflow:ellipsis;z-index:2}[dir=rtl] .md-search__input{padding:0 3.6rem 0 2.2rem}.md-search__input::-webkit-input-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-moz-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input:-ms-input-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-ms-input-placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::placeholder{-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1);transition:color .25s cubic-bezier(.1,.7,.1,1)}.md-search__input::-webkit-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-moz-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input:-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-input-placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:rgba(0,0,0,.54)}.md-search__input::-ms-clear{display:none}.md-search__icon{position:absolute;-webkit-transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:color .25s cubic-bezier(.1,.7,.1,1),opacity .25s;font-size:1.2rem;cursor:pointer;z-index:2}.md-search__icon:hover{opacity:.7}.md-search__icon[for=__search]{top:.3rem;left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem;left:auto}.md-search__icon[for=__search]:before{content:"\E8B6"}.md-search__icon[type=reset]{top:.3rem;right:.5rem;-webkit-transform:scale(.125);transform:scale(.125);-webkit-transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s;transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1);opacity:0}[dir=rtl] .md-search__icon[type=reset]{right:auto;left:.5rem}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]{-webkit-transform:scale(1);transform:scale(1);opacity:1}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__icon[type=reset]:hover{opacity:.7}.md-search__output{position:absolute;width:100%;border-radius:0 0 .1rem .1rem;overflow:hidden;z-index:1}.md-search__scrollwrap{height:100%;background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07);overflow-y:auto;-webkit-overflow-scrolling:touch}.md-search-result{color:rgba(0,0,0,.87);word-break:break-word}.md-search-result__meta{padding:0 .8rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.8rem}.md-search-result__list{margin:0;padding:0;border-top:.05rem solid rgba(0,0,0,.07);list-style:none}.md-search-result__item{box-shadow:0 -.05rem 0 rgba(0,0,0,.07)}.md-search-result__link{display:block;-webkit-transition:background .25s;transition:background .25s;outline:0;overflow:hidden}.md-search-result__link:hover,.md-search-result__link[data-md-state=active]{background-color:rgba(83,109,254,.1)}.md-search-result__link:hover .md-search-result__article:before,.md-search-result__link[data-md-state=active] .md-search-result__article:before{opacity:.7}.md-search-result__link:last-child .md-search-result__teaser{margin-bottom:.6rem}.md-search-result__article{position:relative;padding:0 .8rem;overflow:auto}.md-search-result__article--document:before{position:absolute;left:0;margin:.1rem;-webkit-transition:opacity .25s;transition:opacity .25s;color:rgba(0,0,0,.54);content:"\E880"}[dir=rtl] .md-search-result__article--document:before{right:0;left:auto}.md-search-result__article--document .md-search-result__title{margin:.55rem 0;font-size:.8rem;font-weight:400;line-height:1.4}.md-search-result__title{margin:.5em 0;font-size:.64rem;font-weight:700;line-height:1.4}.md-search-result__teaser{display:-webkit-box;max-height:1.65rem;margin:.5em 0;color:rgba(0,0,0,.54);font-size:.64rem;line-height:1.4;text-overflow:ellipsis;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.md-search-result em{font-style:normal;font-weight:700;text-decoration:underline}.md-sidebar{position:absolute;width:12.1rem;padding:1.2rem 0;overflow:hidden}.md-sidebar[data-md-state=lock]{position:fixed;top:2.4rem}.md-sidebar--secondary{display:none}.md-sidebar__scrollwrap{max-height:100%;margin:0 .2rem;overflow-y:auto;-webkit-backface-visibility:hidden;backface-visibility:hidden}.md-sidebar__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}@-webkit-keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@keyframes md-source__facts--done{0%{height:0}to{height:.65rem}}@-webkit-keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes md-source__fact--done{0%{-webkit-transform:translateY(100%);transform:translateY(100%);opacity:0}50%{opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}.md-source{display:block;padding-right:.6rem;-webkit-transition:opacity .25s;transition:opacity .25s;font-size:.65rem;line-height:1.2;white-space:nowrap}[dir=rtl] .md-source{padding-right:0;padding-left:.6rem}.md-source:hover{opacity:.7}.md-source:after,.md-source__icon{display:inline-block;height:2.4rem;content:"";vertical-align:middle}.md-source__icon{width:2.4rem}.md-source__icon svg{width:1.2rem;height:1.2rem;margin-top:.6rem;margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem;margin-left:0}.md-source__icon+.md-source__repository{margin-left:-2rem;padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem;margin-left:0;padding-right:2rem;padding-left:0}.md-source__repository{display:inline-block;max-width:100%;margin-left:.6rem;font-weight:700;text-overflow:ellipsis;overflow:hidden;vertical-align:middle}.md-source__facts{margin:0;padding:0;font-size:.55rem;font-weight:700;list-style-type:none;opacity:.75;overflow:hidden}[data-md-state=done] .md-source__facts{-webkit-animation:md-source__facts--done .25s ease-in;animation:md-source__facts--done .25s ease-in}.md-source__fact{float:left}[dir=rtl] .md-source__fact{float:right}[data-md-state=done] .md-source__fact{-webkit-animation:md-source__fact--done .4s ease-out;animation:md-source__fact--done .4s ease-out}.md-source__fact:before{margin:0 .1rem;content:"\00B7"}.md-source__fact:first-child:before{display:none}.md-source-file{display:inline-block;margin:1em .5em 1em 0;padding-right:.25rem;border-radius:.1rem;background-color:rgba(0,0,0,.07);font-size:.64rem;list-style-type:none;cursor:pointer;overflow:hidden}.md-source-file:before{display:inline-block;margin-right:.25rem;padding:.25rem;background-color:rgba(0,0,0,.26);color:#fff;font-size:.8rem;content:"\E86F";vertical-align:middle}html .md-source-file{-webkit-transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1);transition:background .4s,color .4s,box-shadow .4s cubic-bezier(.4,0,.2,1)}html .md-source-file:before{-webkit-transition:inherit;transition:inherit}html body .md-typeset .md-source-file{color:rgba(0,0,0,.54)}.md-source-file:hover{box-shadow:0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36)}.md-source-file:hover:before{background-color:#536dfe}.md-tabs{width:100%;-webkit-transition:background .25s;transition:background .25s;background-color:#3f51b5;color:#fff;overflow:auto}.md-tabs__list{margin:0 0 0 .2rem;padding:0;list-style:none;white-space:nowrap}.md-tabs__item{display:inline-block;height:2.4rem;padding-right:.6rem;padding-left:.6rem}.md-tabs__link{display:block;margin-top:.8rem;-webkit-transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s,-webkit-transform .4s cubic-bezier(.1,.7,.1,1);font-size:.7rem;opacity:.7}.md-tabs__link--active,.md-tabs__link:hover{color:inherit;opacity:1}.md-tabs__item:nth-child(2) .md-tabs__link{-webkit-transition-delay:.02s;transition-delay:.02s}.md-tabs__item:nth-child(3) .md-tabs__link{-webkit-transition-delay:.04s;transition-delay:.04s}.md-tabs__item:nth-child(4) .md-tabs__link{-webkit-transition-delay:.06s;transition-delay:.06s}.md-tabs__item:nth-child(5) .md-tabs__link{-webkit-transition-delay:.08s;transition-delay:.08s}.md-tabs__item:nth-child(6) .md-tabs__link{-webkit-transition-delay:.1s;transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{-webkit-transition-delay:.12s;transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{-webkit-transition-delay:.14s;transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{-webkit-transition-delay:.16s;transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{-webkit-transition-delay:.18s;transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{-webkit-transition-delay:.2s;transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{-webkit-transition-delay:.22s;transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{-webkit-transition-delay:.24s;transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{-webkit-transition-delay:.26s;transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{-webkit-transition-delay:.28s;transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{-webkit-transition-delay:.3s;transition-delay:.3s}.md-tabs[data-md-state=hidden]{pointer-events:none}.md-tabs[data-md-state=hidden] .md-tabs__link{-webkit-transform:translateY(50%);transform:translateY(50%);-webkit-transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,opacity .1s,-webkit-transform 0s .4s;transition:color .25s,transform 0s .4s,opacity .1s;transition:color .25s,transform 0s .4s,opacity .1s,-webkit-transform 0s .4s;opacity:0}.md-typeset .admonition,.md-typeset details{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin:1.5625em 0;padding:0 .6rem;border-left:.2rem solid #448aff;border-radius:.1rem;font-size:.64rem;overflow:auto}[dir=rtl] .md-typeset .admonition,[dir=rtl] .md-typeset details{border-right:.2rem solid #448aff;border-left:none}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin:1em 0}.md-typeset .admonition>.admonition-title,.md-typeset .admonition>summary,.md-typeset details>.admonition-title,.md-typeset details>summary{margin:0 -.6rem;padding:.4rem .6rem .4rem 2rem;border-bottom:.05rem solid rgba(68,138,255,.1);background-color:rgba(68,138,255,.1);font-weight:700}[dir=rtl] .md-typeset .admonition>.admonition-title,[dir=rtl] .md-typeset .admonition>summary,[dir=rtl] .md-typeset details>.admonition-title,[dir=rtl] .md-typeset details>summary{padding:.4rem 2rem .4rem .6rem}.md-typeset .admonition>.admonition-title:last-child,.md-typeset .admonition>summary:last-child,.md-typeset details>.admonition-title:last-child,.md-typeset details>summary:last-child{margin-bottom:0}.md-typeset .admonition>.admonition-title:before,.md-typeset .admonition>summary:before,.md-typeset details>.admonition-title:before,.md-typeset details>summary:before{position:absolute;left:.6rem;color:#448aff;font-size:1rem;content:"\E3C9"}[dir=rtl] .md-typeset .admonition>.admonition-title:before,[dir=rtl] .md-typeset .admonition>summary:before,[dir=rtl] .md-typeset details>.admonition-title:before,[dir=rtl] .md-typeset details>summary:before{right:.6rem;left:auto}.md-typeset .admonition.abstract,.md-typeset .admonition.summary,.md-typeset .admonition.tldr,.md-typeset details.abstract,.md-typeset details.summary,.md-typeset details.tldr{border-left-color:#00b0ff}[dir=rtl] .md-typeset .admonition.abstract,[dir=rtl] .md-typeset .admonition.summary,[dir=rtl] .md-typeset .admonition.tldr,[dir=rtl] .md-typeset details.abstract,[dir=rtl] .md-typeset details.summary,[dir=rtl] .md-typeset details.tldr{border-right-color:#00b0ff}.md-typeset .admonition.abstract>.admonition-title,.md-typeset .admonition.abstract>summary,.md-typeset .admonition.summary>.admonition-title,.md-typeset .admonition.summary>summary,.md-typeset .admonition.tldr>.admonition-title,.md-typeset .admonition.tldr>summary,.md-typeset details.abstract>.admonition-title,.md-typeset details.abstract>summary,.md-typeset details.summary>.admonition-title,.md-typeset details.summary>summary,.md-typeset details.tldr>.admonition-title,.md-typeset details.tldr>summary{border-bottom-color:rgba(0,176,255,.1);background-color:rgba(0,176,255,.1)}.md-typeset .admonition.abstract>.admonition-title:before,.md-typeset .admonition.abstract>summary:before,.md-typeset .admonition.summary>.admonition-title:before,.md-typeset .admonition.summary>summary:before,.md-typeset .admonition.tldr>.admonition-title:before,.md-typeset .admonition.tldr>summary:before,.md-typeset details.abstract>.admonition-title:before,.md-typeset details.abstract>summary:before,.md-typeset details.summary>.admonition-title:before,.md-typeset details.summary>summary:before,.md-typeset details.tldr>.admonition-title:before,.md-typeset details.tldr>summary:before{color:#00b0ff;content:""}.md-typeset .admonition.info,.md-typeset .admonition.todo,.md-typeset details.info,.md-typeset details.todo{border-left-color:#00b8d4}[dir=rtl] .md-typeset .admonition.info,[dir=rtl] .md-typeset .admonition.todo,[dir=rtl] .md-typeset details.info,[dir=rtl] .md-typeset details.todo{border-right-color:#00b8d4}.md-typeset .admonition.info>.admonition-title,.md-typeset .admonition.info>summary,.md-typeset .admonition.todo>.admonition-title,.md-typeset .admonition.todo>summary,.md-typeset details.info>.admonition-title,.md-typeset details.info>summary,.md-typeset details.todo>.admonition-title,.md-typeset details.todo>summary{border-bottom-color:rgba(0,184,212,.1);background-color:rgba(0,184,212,.1)}.md-typeset .admonition.info>.admonition-title:before,.md-typeset .admonition.info>summary:before,.md-typeset .admonition.todo>.admonition-title:before,.md-typeset .admonition.todo>summary:before,.md-typeset details.info>.admonition-title:before,.md-typeset details.info>summary:before,.md-typeset details.todo>.admonition-title:before,.md-typeset details.todo>summary:before{color:#00b8d4;content:""}.md-typeset .admonition.hint,.md-typeset .admonition.important,.md-typeset .admonition.tip,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.tip{border-left-color:#00bfa5}[dir=rtl] .md-typeset .admonition.hint,[dir=rtl] .md-typeset .admonition.important,[dir=rtl] .md-typeset .admonition.tip,[dir=rtl] .md-typeset details.hint,[dir=rtl] .md-typeset details.important,[dir=rtl] .md-typeset details.tip{border-right-color:#00bfa5}.md-typeset .admonition.hint>.admonition-title,.md-typeset .admonition.hint>summary,.md-typeset .admonition.important>.admonition-title,.md-typeset .admonition.important>summary,.md-typeset .admonition.tip>.admonition-title,.md-typeset .admonition.tip>summary,.md-typeset details.hint>.admonition-title,.md-typeset details.hint>summary,.md-typeset details.important>.admonition-title,.md-typeset details.important>summary,.md-typeset details.tip>.admonition-title,.md-typeset details.tip>summary{border-bottom-color:rgba(0,191,165,.1);background-color:rgba(0,191,165,.1)}.md-typeset .admonition.hint>.admonition-title:before,.md-typeset .admonition.hint>summary:before,.md-typeset .admonition.important>.admonition-title:before,.md-typeset .admonition.important>summary:before,.md-typeset .admonition.tip>.admonition-title:before,.md-typeset .admonition.tip>summary:before,.md-typeset details.hint>.admonition-title:before,.md-typeset details.hint>summary:before,.md-typeset details.important>.admonition-title:before,.md-typeset details.important>summary:before,.md-typeset details.tip>.admonition-title:before,.md-typeset details.tip>summary:before{color:#00bfa5;content:""}.md-typeset .admonition.check,.md-typeset .admonition.done,.md-typeset .admonition.success,.md-typeset details.check,.md-typeset details.done,.md-typeset details.success{border-left-color:#00c853}[dir=rtl] .md-typeset .admonition.check,[dir=rtl] .md-typeset .admonition.done,[dir=rtl] .md-typeset .admonition.success,[dir=rtl] .md-typeset details.check,[dir=rtl] .md-typeset details.done,[dir=rtl] .md-typeset details.success{border-right-color:#00c853}.md-typeset .admonition.check>.admonition-title,.md-typeset .admonition.check>summary,.md-typeset .admonition.done>.admonition-title,.md-typeset .admonition.done>summary,.md-typeset .admonition.success>.admonition-title,.md-typeset .admonition.success>summary,.md-typeset details.check>.admonition-title,.md-typeset details.check>summary,.md-typeset details.done>.admonition-title,.md-typeset details.done>summary,.md-typeset details.success>.admonition-title,.md-typeset details.success>summary{border-bottom-color:rgba(0,200,83,.1);background-color:rgba(0,200,83,.1)}.md-typeset .admonition.check>.admonition-title:before,.md-typeset .admonition.check>summary:before,.md-typeset .admonition.done>.admonition-title:before,.md-typeset .admonition.done>summary:before,.md-typeset .admonition.success>.admonition-title:before,.md-typeset .admonition.success>summary:before,.md-typeset details.check>.admonition-title:before,.md-typeset details.check>summary:before,.md-typeset details.done>.admonition-title:before,.md-typeset details.done>summary:before,.md-typeset details.success>.admonition-title:before,.md-typeset details.success>summary:before{color:#00c853;content:""}.md-typeset .admonition.faq,.md-typeset .admonition.help,.md-typeset .admonition.question,.md-typeset details.faq,.md-typeset details.help,.md-typeset details.question{border-left-color:#64dd17}[dir=rtl] .md-typeset .admonition.faq,[dir=rtl] .md-typeset .admonition.help,[dir=rtl] .md-typeset .admonition.question,[dir=rtl] .md-typeset details.faq,[dir=rtl] .md-typeset details.help,[dir=rtl] .md-typeset details.question{border-right-color:#64dd17}.md-typeset .admonition.faq>.admonition-title,.md-typeset .admonition.faq>summary,.md-typeset .admonition.help>.admonition-title,.md-typeset .admonition.help>summary,.md-typeset .admonition.question>.admonition-title,.md-typeset .admonition.question>summary,.md-typeset details.faq>.admonition-title,.md-typeset details.faq>summary,.md-typeset details.help>.admonition-title,.md-typeset details.help>summary,.md-typeset details.question>.admonition-title,.md-typeset details.question>summary{border-bottom-color:rgba(100,221,23,.1);background-color:rgba(100,221,23,.1)}.md-typeset .admonition.faq>.admonition-title:before,.md-typeset .admonition.faq>summary:before,.md-typeset .admonition.help>.admonition-title:before,.md-typeset .admonition.help>summary:before,.md-typeset .admonition.question>.admonition-title:before,.md-typeset .admonition.question>summary:before,.md-typeset details.faq>.admonition-title:before,.md-typeset details.faq>summary:before,.md-typeset details.help>.admonition-title:before,.md-typeset details.help>summary:before,.md-typeset details.question>.admonition-title:before,.md-typeset details.question>summary:before{color:#64dd17;content:""}.md-typeset .admonition.attention,.md-typeset .admonition.caution,.md-typeset .admonition.warning,.md-typeset details.attention,.md-typeset details.caution,.md-typeset details.warning{border-left-color:#ff9100}[dir=rtl] .md-typeset .admonition.attention,[dir=rtl] .md-typeset .admonition.caution,[dir=rtl] .md-typeset .admonition.warning,[dir=rtl] .md-typeset details.attention,[dir=rtl] .md-typeset details.caution,[dir=rtl] .md-typeset details.warning{border-right-color:#ff9100}.md-typeset .admonition.attention>.admonition-title,.md-typeset .admonition.attention>summary,.md-typeset .admonition.caution>.admonition-title,.md-typeset .admonition.caution>summary,.md-typeset .admonition.warning>.admonition-title,.md-typeset .admonition.warning>summary,.md-typeset details.attention>.admonition-title,.md-typeset details.attention>summary,.md-typeset details.caution>.admonition-title,.md-typeset details.caution>summary,.md-typeset details.warning>.admonition-title,.md-typeset details.warning>summary{border-bottom-color:rgba(255,145,0,.1);background-color:rgba(255,145,0,.1)}.md-typeset .admonition.attention>.admonition-title:before,.md-typeset .admonition.attention>summary:before,.md-typeset .admonition.caution>.admonition-title:before,.md-typeset .admonition.caution>summary:before,.md-typeset .admonition.warning>.admonition-title:before,.md-typeset .admonition.warning>summary:before,.md-typeset details.attention>.admonition-title:before,.md-typeset details.attention>summary:before,.md-typeset details.caution>.admonition-title:before,.md-typeset details.caution>summary:before,.md-typeset details.warning>.admonition-title:before,.md-typeset details.warning>summary:before{color:#ff9100;content:""}.md-typeset .admonition.fail,.md-typeset .admonition.failure,.md-typeset .admonition.missing,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.missing{border-left-color:#ff5252}[dir=rtl] .md-typeset .admonition.fail,[dir=rtl] .md-typeset .admonition.failure,[dir=rtl] .md-typeset .admonition.missing,[dir=rtl] .md-typeset details.fail,[dir=rtl] .md-typeset details.failure,[dir=rtl] .md-typeset details.missing{border-right-color:#ff5252}.md-typeset .admonition.fail>.admonition-title,.md-typeset .admonition.fail>summary,.md-typeset .admonition.failure>.admonition-title,.md-typeset .admonition.failure>summary,.md-typeset .admonition.missing>.admonition-title,.md-typeset .admonition.missing>summary,.md-typeset details.fail>.admonition-title,.md-typeset details.fail>summary,.md-typeset details.failure>.admonition-title,.md-typeset details.failure>summary,.md-typeset details.missing>.admonition-title,.md-typeset details.missing>summary{border-bottom-color:rgba(255,82,82,.1);background-color:rgba(255,82,82,.1)}.md-typeset .admonition.fail>.admonition-title:before,.md-typeset .admonition.fail>summary:before,.md-typeset .admonition.failure>.admonition-title:before,.md-typeset .admonition.failure>summary:before,.md-typeset .admonition.missing>.admonition-title:before,.md-typeset .admonition.missing>summary:before,.md-typeset details.fail>.admonition-title:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>.admonition-title:before,.md-typeset details.failure>summary:before,.md-typeset details.missing>.admonition-title:before,.md-typeset details.missing>summary:before{color:#ff5252;content:""}.md-typeset .admonition.danger,.md-typeset .admonition.error,.md-typeset details.danger,.md-typeset details.error{border-left-color:#ff1744}[dir=rtl] .md-typeset .admonition.danger,[dir=rtl] .md-typeset .admonition.error,[dir=rtl] .md-typeset details.danger,[dir=rtl] .md-typeset details.error{border-right-color:#ff1744}.md-typeset .admonition.danger>.admonition-title,.md-typeset .admonition.danger>summary,.md-typeset .admonition.error>.admonition-title,.md-typeset .admonition.error>summary,.md-typeset details.danger>.admonition-title,.md-typeset details.danger>summary,.md-typeset details.error>.admonition-title,.md-typeset details.error>summary{border-bottom-color:rgba(255,23,68,.1);background-color:rgba(255,23,68,.1)}.md-typeset .admonition.danger>.admonition-title:before,.md-typeset .admonition.danger>summary:before,.md-typeset .admonition.error>.admonition-title:before,.md-typeset .admonition.error>summary:before,.md-typeset details.danger>.admonition-title:before,.md-typeset details.danger>summary:before,.md-typeset details.error>.admonition-title:before,.md-typeset details.error>summary:before{color:#ff1744;content:""}.md-typeset .admonition.bug,.md-typeset details.bug{border-left-color:#f50057}[dir=rtl] .md-typeset .admonition.bug,[dir=rtl] .md-typeset details.bug{border-right-color:#f50057}.md-typeset .admonition.bug>.admonition-title,.md-typeset .admonition.bug>summary,.md-typeset details.bug>.admonition-title,.md-typeset details.bug>summary{border-bottom-color:rgba(245,0,87,.1);background-color:rgba(245,0,87,.1)}.md-typeset .admonition.bug>.admonition-title:before,.md-typeset .admonition.bug>summary:before,.md-typeset details.bug>.admonition-title:before,.md-typeset details.bug>summary:before{color:#f50057;content:""}.md-typeset .admonition.example,.md-typeset details.example{border-left-color:#651fff}[dir=rtl] .md-typeset .admonition.example,[dir=rtl] .md-typeset details.example{border-right-color:#651fff}.md-typeset .admonition.example>.admonition-title,.md-typeset .admonition.example>summary,.md-typeset details.example>.admonition-title,.md-typeset details.example>summary{border-bottom-color:rgba(101,31,255,.1);background-color:rgba(101,31,255,.1)}.md-typeset .admonition.example>.admonition-title:before,.md-typeset .admonition.example>summary:before,.md-typeset details.example>.admonition-title:before,.md-typeset details.example>summary:before{color:#651fff;content:""}.md-typeset .admonition.cite,.md-typeset .admonition.quote,.md-typeset details.cite,.md-typeset details.quote{border-left-color:#9e9e9e}[dir=rtl] .md-typeset .admonition.cite,[dir=rtl] .md-typeset .admonition.quote,[dir=rtl] .md-typeset details.cite,[dir=rtl] .md-typeset details.quote{border-right-color:#9e9e9e}.md-typeset .admonition.cite>.admonition-title,.md-typeset .admonition.cite>summary,.md-typeset .admonition.quote>.admonition-title,.md-typeset .admonition.quote>summary,.md-typeset details.cite>.admonition-title,.md-typeset details.cite>summary,.md-typeset details.quote>.admonition-title,.md-typeset details.quote>summary{border-bottom-color:hsla(0,0%,62%,.1);background-color:hsla(0,0%,62%,.1)}.md-typeset .admonition.cite>.admonition-title:before,.md-typeset .admonition.cite>summary:before,.md-typeset .admonition.quote>.admonition-title:before,.md-typeset .admonition.quote>summary:before,.md-typeset details.cite>.admonition-title:before,.md-typeset details.cite>summary:before,.md-typeset details.quote>.admonition-title:before,.md-typeset details.quote>summary:before{color:#9e9e9e;content:""}.codehilite .o,.codehilite .ow,.md-typeset .highlight .o,.md-typeset .highlight .ow{color:inherit}.codehilite .ge,.md-typeset .highlight .ge{color:#000}.codehilite .gr,.md-typeset .highlight .gr{color:#a00}.codehilite .gh,.md-typeset .highlight .gh{color:#999}.codehilite .go,.md-typeset .highlight .go{color:#888}.codehilite .gp,.md-typeset .highlight .gp{color:#555}.codehilite .gs,.md-typeset .highlight .gs{color:inherit}.codehilite .gu,.md-typeset .highlight .gu{color:#aaa}.codehilite .gt,.md-typeset .highlight .gt{color:#a00}.codehilite .gd,.md-typeset .highlight .gd{background-color:#fdd}.codehilite .gi,.md-typeset .highlight .gi{background-color:#dfd}.codehilite .k,.md-typeset .highlight .k{color:#3b78e7}.codehilite .kc,.md-typeset .highlight .kc{color:#a71d5d}.codehilite .kd,.codehilite .kn,.md-typeset .highlight .kd,.md-typeset .highlight .kn{color:#3b78e7}.codehilite .kp,.md-typeset .highlight .kp{color:#a71d5d}.codehilite .kr,.codehilite .kt,.md-typeset .highlight .kr,.md-typeset .highlight .kt{color:#3e61a2}.codehilite .c,.codehilite .cm,.md-typeset .highlight .c,.md-typeset .highlight .cm{color:#999}.codehilite .cp,.md-typeset .highlight .cp{color:#666}.codehilite .c1,.codehilite .ch,.codehilite .cs,.md-typeset .highlight .c1,.md-typeset .highlight .ch,.md-typeset .highlight .cs{color:#999}.codehilite .na,.codehilite .nb,.md-typeset .highlight .na,.md-typeset .highlight .nb{color:#c2185b}.codehilite .bp,.md-typeset .highlight .bp{color:#3e61a2}.codehilite .nc,.md-typeset .highlight .nc{color:#c2185b}.codehilite .no,.md-typeset .highlight .no{color:#3e61a2}.codehilite .nd,.codehilite .ni,.md-typeset .highlight .nd,.md-typeset .highlight .ni{color:#666}.codehilite .ne,.codehilite .nf,.md-typeset .highlight .ne,.md-typeset .highlight .nf{color:#c2185b}.codehilite .nl,.md-typeset .highlight .nl{color:#3b5179}.codehilite .nn,.md-typeset .highlight .nn{color:#ec407a}.codehilite .nt,.md-typeset .highlight .nt{color:#3b78e7}.codehilite .nv,.codehilite .vc,.codehilite .vg,.codehilite .vi,.md-typeset .highlight .nv,.md-typeset .highlight .vc,.md-typeset .highlight .vg,.md-typeset .highlight .vi{color:#3e61a2}.codehilite .nx,.md-typeset .highlight .nx{color:#ec407a}.codehilite .il,.codehilite .m,.codehilite .mf,.codehilite .mh,.codehilite .mi,.codehilite .mo,.md-typeset .highlight .il,.md-typeset .highlight .m,.md-typeset .highlight .mf,.md-typeset .highlight .mh,.md-typeset .highlight .mi,.md-typeset .highlight .mo{color:#e74c3c}.codehilite .s,.codehilite .sb,.codehilite .sc,.md-typeset .highlight .s,.md-typeset .highlight .sb,.md-typeset .highlight .sc{color:#0d904f}.codehilite .sd,.md-typeset .highlight .sd{color:#999}.codehilite .s2,.md-typeset .highlight .s2{color:#0d904f}.codehilite .se,.codehilite .sh,.codehilite .si,.codehilite .sx,.md-typeset .highlight .se,.md-typeset .highlight .sh,.md-typeset .highlight .si,.md-typeset .highlight .sx{color:#183691}.codehilite .sr,.md-typeset .highlight .sr{color:#009926}.codehilite .s1,.codehilite .ss,.md-typeset .highlight .s1,.md-typeset .highlight .ss{color:#0d904f}.codehilite .err,.md-typeset .highlight .err{color:#a61717}.codehilite .w,.md-typeset .highlight .w{color:transparent}.codehilite .hll,.md-typeset .highlight .hll{display:block;margin:0 -.6rem;padding:0 .6rem;background-color:rgba(255,235,59,.5)}.md-typeset .codehilite,.md-typeset .highlight{position:relative;margin:1em 0;padding:0;border-radius:.1rem;background-color:hsla(0,0%,92.5%,.5);color:#37474f;line-height:1.4;-webkit-overflow-scrolling:touch}.md-typeset .codehilite code,.md-typeset .codehilite pre,.md-typeset .highlight code,.md-typeset .highlight pre{display:block;margin:0;padding:.525rem .6rem;background-color:transparent;overflow:auto;vertical-align:top}.md-typeset .codehilite code::-webkit-scrollbar,.md-typeset .codehilite pre::-webkit-scrollbar,.md-typeset .highlight code::-webkit-scrollbar,.md-typeset .highlight pre::-webkit-scrollbar{width:.2rem;height:.2rem}.md-typeset .codehilite code::-webkit-scrollbar-thumb,.md-typeset .codehilite pre::-webkit-scrollbar-thumb,.md-typeset .highlight code::-webkit-scrollbar-thumb,.md-typeset .highlight pre::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-typeset .codehilite code::-webkit-scrollbar-thumb:hover,.md-typeset .codehilite pre::-webkit-scrollbar-thumb:hover,.md-typeset .highlight code::-webkit-scrollbar-thumb:hover,.md-typeset .highlight pre::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-typeset pre.codehilite,.md-typeset pre.highlight{overflow:visible}.md-typeset pre.codehilite code,.md-typeset pre.highlight code{display:block;padding:.525rem .6rem;overflow:auto}.md-typeset .codehilitetable,.md-typeset .highlighttable{display:block;margin:1em 0;border-radius:.2em;font-size:.8rem;overflow:hidden}.md-typeset .codehilitetable tbody,.md-typeset .codehilitetable td,.md-typeset .highlighttable tbody,.md-typeset .highlighttable td{display:block;padding:0}.md-typeset .codehilitetable tr,.md-typeset .highlighttable tr{display:-webkit-box;display:flex}.md-typeset .codehilitetable .codehilite,.md-typeset .codehilitetable .highlight,.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .codehilite,.md-typeset .highlighttable .highlight,.md-typeset .highlighttable .linenodiv{margin:0;border-radius:0}.md-typeset .codehilitetable .linenodiv,.md-typeset .highlighttable .linenodiv{padding:.525rem .6rem}.md-typeset .codehilitetable .linenos,.md-typeset .highlighttable .linenos{background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.26);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset .codehilitetable .linenos pre,.md-typeset .highlighttable .linenos pre{margin:0;padding:0;background-color:transparent;color:inherit;text-align:right}.md-typeset .codehilitetable .code,.md-typeset .highlighttable .code{-webkit-box-flex:1;flex:1;overflow:hidden}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{box-shadow:none}.md-typeset [id^="fnref:"]{display:inline-block}.md-typeset [id^="fnref:"]:target{margin-top:-3.8rem;padding-top:3.8rem;pointer-events:none}.md-typeset [id^="fn:"]:before{display:none;height:0;content:""}.md-typeset [id^="fn:"]:target:before{display:block;margin-top:-3.5rem;padding-top:3.5rem;pointer-events:none}.md-typeset .footnote{color:rgba(0,0,0,.54);font-size:.64rem}.md-typeset .footnote ol{margin-left:0}.md-typeset .footnote li{-webkit-transition:color .25s;transition:color .25s}.md-typeset .footnote li:target{color:rgba(0,0,0,.87)}.md-typeset .footnote li :first-child{margin-top:0}.md-typeset .footnote li:hover .footnote-backref,.md-typeset .footnote li:target .footnote-backref{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}.md-typeset .footnote li:hover .footnote-backref:hover,.md-typeset .footnote li:target .footnote-backref{color:#536dfe}.md-typeset .footnote-ref{display:inline-block;pointer-events:auto}.md-typeset .footnote-ref:before{display:inline;margin:0 .2em;border-left:.05rem solid rgba(0,0,0,.26);font-size:1.25em;content:"";vertical-align:-.25rem}.md-typeset .footnote-backref{display:inline-block;-webkit-transform:translateX(.25rem);transform:translateX(.25rem);-webkit-transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:color .25s,opacity .125s .125s,-webkit-transform .25s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s;transition:transform .25s .125s,color .25s,opacity .125s .125s,-webkit-transform .25s .125s;color:rgba(0,0,0,.26);font-size:0;opacity:0;vertical-align:text-bottom}[dir=rtl] .md-typeset .footnote-backref{-webkit-transform:translateX(-.25rem);transform:translateX(-.25rem)}.md-typeset .footnote-backref:before{display:inline-block;font-size:.8rem;content:"\E31B"}[dir=rtl] .md-typeset .footnote-backref:before{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.md-typeset .headerlink{display:inline-block;margin-left:.5rem;-webkit-transform:translateY(.25rem);transform:translateY(.25rem);-webkit-transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:color .25s,opacity .125s .25s,-webkit-transform .25s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s;transition:transform .25s .25s,color .25s,opacity .125s .25s,-webkit-transform .25s .25s;opacity:0}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem;margin-left:0}html body .md-typeset .headerlink{color:rgba(0,0,0,.26)}.md-typeset h1[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h1[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink,.md-typeset h1[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h1[id] .headerlink:focus,.md-typeset h1[id]:hover .headerlink:hover,.md-typeset h1[id]:target .headerlink{color:#536dfe}.md-typeset h2[id]:before{display:block;margin-top:-8px;padding-top:8px;content:""}.md-typeset h2[id]:target:before{margin-top:-3.4rem;padding-top:3.4rem}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink,.md-typeset h2[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h2[id] .headerlink:focus,.md-typeset h2[id]:hover .headerlink:hover,.md-typeset h2[id]:target .headerlink{color:#536dfe}.md-typeset h3[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h3[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink,.md-typeset h3[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h3[id] .headerlink:focus,.md-typeset h3[id]:hover .headerlink:hover,.md-typeset h3[id]:target .headerlink{color:#536dfe}.md-typeset h4[id]:before{display:block;margin-top:-9px;padding-top:9px;content:""}.md-typeset h4[id]:target:before{margin-top:-3.45rem;padding-top:3.45rem}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink,.md-typeset h4[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h4[id] .headerlink:focus,.md-typeset h4[id]:hover .headerlink:hover,.md-typeset h4[id]:target .headerlink{color:#536dfe}.md-typeset h5[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h5[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink,.md-typeset h5[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h5[id] .headerlink:focus,.md-typeset h5[id]:hover .headerlink:hover,.md-typeset h5[id]:target .headerlink{color:#536dfe}.md-typeset h6[id]:before{display:block;margin-top:-11px;padding-top:11px;content:""}.md-typeset h6[id]:target:before{margin-top:-3.55rem;padding-top:3.55rem}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink,.md-typeset h6[id]:target .headerlink{-webkit-transform:translate(0);transform:translate(0);opacity:1}.md-typeset h6[id] .headerlink:focus,.md-typeset h6[id]:hover .headerlink:hover,.md-typeset h6[id]:target .headerlink{color:#536dfe}.md-typeset .MJXc-display{margin:.75em 0;padding:.75em 0;overflow:auto;-webkit-overflow-scrolling:touch}.md-typeset .MathJax_CHTML{outline:0}.md-typeset .critic.comment,.md-typeset del.critic,.md-typeset ins.critic{margin:0 .25em;padding:.0625em 0;border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset del.critic{background-color:#fdd;box-shadow:.25em 0 0 #fdd,-.25em 0 0 #fdd}.md-typeset ins.critic{background-color:#dfd;box-shadow:.25em 0 0 #dfd,-.25em 0 0 #dfd}.md-typeset .critic.comment{background-color:hsla(0,0%,92.5%,.5);color:#37474f;box-shadow:.25em 0 0 hsla(0,0%,92.5%,.5),-.25em 0 0 hsla(0,0%,92.5%,.5)}.md-typeset .critic.comment:before{padding-right:.125em;color:rgba(0,0,0,.26);content:"\E0B7";vertical-align:-.125em}.md-typeset .critic.block{display:block;margin:1em 0;padding-right:.8rem;padding-left:.8rem;box-shadow:none}.md-typeset .critic.block :first-child{margin-top:.5em}.md-typeset .critic.block :last-child{margin-bottom:.5em}.md-typeset details{display:block;padding-top:0}.md-typeset details[open]>summary:after{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.md-typeset details:not([open]){padding-bottom:0}.md-typeset details:not([open])>summary{border-bottom:none}.md-typeset details summary{padding-right:2rem}[dir=rtl] .md-typeset details summary{padding-left:2rem}.no-details .md-typeset details:not([open])>*{display:none}.no-details .md-typeset details:not([open]) summary{display:block}.md-typeset summary{display:block;outline:none;cursor:pointer}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset summary:after{position:absolute;top:.4rem;right:.6rem;color:rgba(0,0,0,.26);font-size:1rem;content:"\E313"}[dir=rtl] .md-typeset summary:after{right:auto;left:.6rem}.md-typeset .emojione{width:1rem;vertical-align:text-top}.md-typeset code.codehilite,.md-typeset code.highlight{margin:0 .29412em;padding:.07353em 0}.md-typeset .superfences-content{display:none;-webkit-box-ordinal-group:100;order:99;width:100%;background-color:#fff}.md-typeset .superfences-content>*{margin:0;border-radius:0}.md-typeset .superfences-tabs{display:-webkit-box;display:flex;position:relative;flex-wrap:wrap;margin:1em 0;border:.05rem solid rgba(0,0,0,.07);border-radius:.2em}.md-typeset .superfences-tabs>input{display:none}.md-typeset .superfences-tabs>input:checked+label{font-weight:700}.md-typeset .superfences-tabs>input:checked+label+.superfences-content{display:block}.md-typeset .superfences-tabs>label{width:auto;padding:.6rem;-webkit-transition:color .125s;transition:color .125s;font-size:.64rem;cursor:pointer}html .md-typeset .superfences-tabs>label:hover{color:#536dfe}.md-typeset .task-list-item{position:relative;list-style-type:none}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em;left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em;left:auto}.md-typeset .task-list-control .task-list-indicator:before{position:absolute;top:.15em;left:-1.25em;color:rgba(0,0,0,.26);font-size:1.25em;content:"\E835";vertical-align:-.25em}[dir=rtl] .md-typeset .task-list-control .task-list-indicator:before{right:-1.25em;left:auto}.md-typeset .task-list-control [type=checkbox]:checked+.task-list-indicator:before{content:"\E834"}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}@media print{.md-typeset a:after{color:rgba(0,0,0,.54);content:" [" attr(href) "]"}.md-typeset code,.md-typeset pre{white-space:pre-wrap}.md-typeset code{box-shadow:none;-webkit-box-decoration-break:initial;box-decoration-break:slice}.md-clipboard,.md-content__icon,.md-footer,.md-header,.md-sidebar,.md-tabs,.md-typeset .headerlink{display:none}}@media only screen and (max-width:44.9375em){.md-typeset pre{margin:1em -.8rem;border-radius:0}.md-typeset pre>code{padding:.525rem .8rem}.md-footer-nav__link--prev .md-footer-nav__title{display:none}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}.codehilite .hll,.md-typeset .highlight .hll{margin:0 -.8rem;padding:0 .8rem}.md-typeset>.codehilite,.md-typeset>.highlight{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilite code,.md-typeset>.codehilite pre,.md-typeset>.highlight code,.md-typeset>.highlight pre{padding:.525rem .8rem}.md-typeset>.codehilitetable,.md-typeset>.highlighttable{margin:1em -.8rem;border-radius:0}.md-typeset>.codehilitetable .codehilite>code,.md-typeset>.codehilitetable .codehilite>pre,.md-typeset>.codehilitetable .highlight>code,.md-typeset>.codehilitetable .highlight>pre,.md-typeset>.codehilitetable .linenodiv,.md-typeset>.highlighttable .codehilite>code,.md-typeset>.highlighttable .codehilite>pre,.md-typeset>.highlighttable .highlight>code,.md-typeset>.highlighttable .highlight>pre,.md-typeset>.highlighttable .linenodiv{padding:.5rem .8rem}.md-typeset>p>.MJXc-display{margin:.75em -.8rem;padding:.25em .8rem}.md-typeset>.superfences-tabs{margin:1em -.8rem;border:0;border-top:.05rem solid rgba(0,0,0,.07);border-radius:0}.md-typeset>.superfences-tabs code,.md-typeset>.superfences-tabs pre{padding:.525rem .8rem}}@media only screen and (min-width:100em){html{font-size:137.5%}}@media only screen and (min-width:125em){html{font-size:150%}}@media only screen and (max-width:59.9375em){body[data-md-state=lock]{overflow:hidden}.ios body[data-md-state=lock] .md-container{display:none}html .md-nav__link[for=__toc]{display:block;padding-right:2.4rem}html .md-nav__link[for=__toc]:after{color:inherit;content:"\E8DE"}html .md-nav__link[for=__toc]+.md-nav__link{display:none}html .md-nav__link[for=__toc]~.md-nav{display:-webkit-box;display:flex}html [dir=rtl] .md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav__source{display:block;padding:0 .2rem;background-color:rgba(50,64,144,.9675);color:#fff}.md-search__overlay{position:absolute;top:.2rem;left:.2rem;width:1.8rem;height:1.8rem;-webkit-transform-origin:center;transform-origin:center;-webkit-transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:opacity .2s .2s,-webkit-transform .3s .1s;transition:transform .3s .1s,opacity .2s .2s;transition:transform .3s .1s,opacity .2s .2s,-webkit-transform .3s .1s;border-radius:1rem;background-color:#fff;overflow:hidden;pointer-events:none}[dir=rtl] .md-search__overlay{right:.2rem;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transition:opacity .1s,-webkit-transform .4s;transition:opacity .1s,-webkit-transform .4s;transition:transform .4s,opacity .1s;transition:transform .4s,opacity .1s,-webkit-transform .4s;opacity:1}.md-search__inner{position:fixed;top:0;left:100%;width:100%;height:100%;-webkit-transform:translateX(5%);transform:translateX(5%);-webkit-transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;transition:right 0s .3s,left 0s .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.4,0,.2,1) .15s;opacity:0;z-index:2}[data-md-toggle=search]:checked~.md-header .md-search__inner{left:0;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;transition:right 0s 0s,left 0s 0s,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s,-webkit-transform .15s cubic-bezier(.1,.7,.1,1) .15s;opacity:1}[dir=rtl] [data-md-toggle=search]:checked~.md-header .md-search__inner{right:0;left:auto}html [dir=rtl] .md-search__inner{right:100%;left:auto;-webkit-transform:translateX(-5%);transform:translateX(-5%)}.md-search__input{width:100%;height:2.4rem;font-size:.9rem}.md-search__icon[for=__search]{top:.6rem;left:.8rem}.md-search__icon[for=__search][for=__search]:before{content:"\E5C4"}[dir=rtl] .md-search__icon[for=__search][for=__search]:before{content:"\E5C8"}.md-search__icon[type=reset]{top:.6rem;right:.8rem}.md-search__output{top:2.4rem;bottom:0}.md-search-result__article--document:before{display:none}}@media only screen and (max-width:76.1875em){[data-md-toggle=drawer]:checked~.md-overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-header-nav__button.md-icon--home,.md-header-nav__button.md-logo{display:none}.md-hero__inner{margin-top:2.4rem;margin-bottom:1.2rem}.md-nav{background-color:#fff}.md-nav--primary,.md-nav--primary .md-nav{display:-webkit-box;display:flex;position:absolute;top:0;right:0;left:0;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;height:100%;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}html .md-nav--primary .md-nav__title{position:relative;height:5.6rem;padding:3rem .8rem .2rem;background-color:rgba(0,0,0,.07);color:rgba(0,0,0,.54);font-weight:400;line-height:2.4rem;white-space:nowrap;cursor:pointer}html .md-nav--primary .md-nav__title:before{display:block;position:absolute;top:.2rem;left:.2rem;width:2rem;height:2rem;color:rgba(0,0,0,.54)}html .md-nav--primary .md-nav__title~.md-nav__list{background-color:#fff;box-shadow:inset 0 .05rem 0 rgba(0,0,0,.07)}html .md-nav--primary .md-nav__title~.md-nav__list>.md-nav__item:first-child{border-top:0}html .md-nav--primary .md-nav__title--site{position:relative;background-color:#3f51b5;color:#fff}html .md-nav--primary .md-nav__title--site .md-nav__button{display:block;position:absolute;top:.2rem;left:.2rem;width:3.2rem;height:3.2rem;font-size:2.4rem}html .md-nav--primary .md-nav__title--site:before{display:none}html [dir=rtl] .md-nav--primary .md-nav__title--site .md-nav__button,html [dir=rtl] .md-nav--primary .md-nav__title:before{right:.2rem;left:auto}.md-nav--primary .md-nav__list{-webkit-box-flex:1;flex:1;overflow-y:auto}.md-nav--primary .md-nav__item{padding:0;border-top:.05rem solid rgba(0,0,0,.07)}[dir=rtl] .md-nav--primary .md-nav__item{padding:0}.md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:2.4rem}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link{padding-right:.8rem;padding-left:2.4rem}.md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E315"}[dir=rtl] .md-nav--primary .md-nav__item--nested>.md-nav__link:after{content:"\E314"}.md-nav--primary .md-nav__link{position:relative;margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link:after{position:absolute;top:50%;right:.6rem;margin-top:-.6rem;color:inherit;font-size:1.2rem}[dir=rtl] .md-nav--primary .md-nav__link:after{right:auto;left:.6rem}.md-nav--primary .md-nav--secondary .md-nav__link{position:static}.md-nav--primary .md-nav--secondary .md-nav{position:static;background-color:transparent}.md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem;padding-left:0}.md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem;padding-left:0}.md-nav__toggle~.md-nav{display:-webkit-box;display:flex;-webkit-transform:translateX(100%);transform:translateX(100%);-webkit-transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s;transition:transform .25s cubic-bezier(.8,0,.6,1),opacity .125s .05s,-webkit-transform .25s cubic-bezier(.8,0,.6,1);opacity:0}[dir=rtl] .md-nav__toggle~.md-nav{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.no-csstransforms3d .md-nav__toggle~.md-nav{display:none}.md-nav__toggle:checked~.md-nav{-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .125s .125s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);opacity:1}.no-csstransforms3d .md-nav__toggle:checked~.md-nav{display:-webkit-box;display:flex}.md-sidebar--primary{position:fixed;top:0;left:-12.1rem;width:12.1rem;height:100%;-webkit-transform:translateX(0);transform:translateX(0);-webkit-transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s,-webkit-transform .25s cubic-bezier(.4,0,.2,1);background-color:#fff;z-index:3}[dir=rtl] .md-sidebar--primary{right:-12.1rem;left:auto}.no-csstransforms3d .md-sidebar--primary{display:none}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12),0 5px 5px -3px rgba(0,0,0,.4);-webkit-transform:translateX(12.1rem);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{-webkit-transform:translateX(-12.1rem);transform:translateX(-12.1rem)}.no-csstransforms3d [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{display:block}.md-sidebar--primary .md-sidebar__scrollwrap{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;margin:0}.md-tabs{display:none}}@media only screen and (min-width:60em){.md-content{margin-right:12.1rem}[dir=rtl] .md-content{margin-right:0;margin-left:12.1rem}.md-header-nav__button.md-icon--search{display:none}.md-header-nav__source{display:block;width:11.7rem;max-width:11.7rem;padding-right:.6rem}[dir=rtl] .md-header-nav__source{padding-right:0;padding-left:.6rem}.md-search{padding:.2rem}.md-search__overlay{position:fixed;top:0;left:0;width:0;height:0;-webkit-transition:width 0s .25s,height 0s .25s,opacity .25s;transition:width 0s .25s,height 0s .25s,opacity .25s;background-color:rgba(0,0,0,.54);cursor:pointer}[dir=rtl] .md-search__overlay{right:0;left:auto}[data-md-toggle=search]:checked~.md-header .md-search__overlay{width:100%;height:100%;-webkit-transition:width 0s,height 0s,opacity .25s;transition:width 0s,height 0s,opacity .25s;opacity:1}.md-search__inner{position:relative;width:11.5rem;margin-right:.8rem;padding:.1rem 0;float:right;-webkit-transition:width .25s cubic-bezier(.1,.7,.1,1);transition:width .25s cubic-bezier(.1,.7,.1,1)}[dir=rtl] .md-search__inner{margin-right:0;margin-left:.8rem;float:left}.md-search__form,.md-search__input{border-radius:.1rem}.md-search__input{width:100%;height:1.8rem;padding-left:2.2rem;-webkit-transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);transition:background-color .25s cubic-bezier(.1,.7,.1,1),color .25s cubic-bezier(.1,.7,.1,1);background-color:rgba(0,0,0,.26);color:inherit;font-size:.8rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input+.md-search__icon{color:inherit}.md-search__input::-webkit-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-moz-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::-ms-input-placeholder{color:hsla(0,0%,100%,.7)}.md-search__input::placeholder{color:hsla(0,0%,100%,.7)}.md-search__input:hover{background-color:hsla(0,0%,100%,.12)}[data-md-toggle=search]:checked~.md-header .md-search__input{border-radius:.1rem .1rem 0 0;background-color:#fff;color:rgba(0,0,0,.87);text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-webkit-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-moz-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input:-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::-ms-input-placeholder{color:rgba(0,0,0,.54)}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon,[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:rgba(0,0,0,.54)}.md-search__output{top:1.9rem;-webkit-transition:opacity .4s;transition:opacity .4s;opacity:0}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12),0 3px 5px -1px rgba(0,0,0,.4);opacity:1}.md-search__scrollwrap{max-height:0}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap::-webkit-scrollbar{width:.2rem;height:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.26)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#536dfe}.md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem;padding-left:0}.md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem;padding-left:.8rem}.md-sidebar--secondary{display:block;margin-left:100%;-webkit-transform:translate(-100%);transform:translate(-100%)}[dir=rtl] .md-sidebar--secondary{margin-right:100%;margin-left:0;-webkit-transform:translate(100%);transform:translate(100%)}}@media only screen and (min-width:76.25em){.md-content{margin-left:12.1rem}[dir=rtl] .md-content{margin-right:12.1rem}.md-content__inner{margin-right:1.2rem;margin-left:1.2rem}.md-header-nav__button.md-icon--menu{display:none}.md-nav[data-md-state=animate]{-webkit-transition:max-height .25s cubic-bezier(.86,0,.07,1);transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav__toggle~.md-nav{max-height:0;overflow:hidden}.no-js .md-nav__toggle~.md-nav{display:none}.md-nav[data-md-state=expand],.md-nav__toggle:checked~.md-nav{max-height:100%}.no-js .md-nav[data-md-state=expand],.no-js .md-nav__toggle:checked~.md-nav{display:block}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--nested>.md-nav__link:after{display:inline-block;-webkit-transform-origin:.45em .45em;transform-origin:.45em .45em;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;vertical-align:-.125em}.js .md-nav__item--nested>.md-nav__link:after{-webkit-transition:-webkit-transform .4s;transition:-webkit-transform .4s;transition:transform .4s;transition:transform .4s,-webkit-transform .4s}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link:after{-webkit-transform:rotateX(180deg);transform:rotateX(180deg)}.md-search__inner{margin-right:1.2rem}[dir=rtl] .md-search__inner{margin-left:1.2rem}.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}.md-sidebar--secondary{margin-left:61rem}[dir=rtl] .md-sidebar--secondary{margin-right:61rem;margin-left:0}.md-tabs~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary .md-nav__title{display:block;padding:0}.md-tabs--active~.md-main .md-nav--primary .md-nav__title--site{display:none}.no-js .md-tabs--active~.md-main .md-nav--primary .md-nav{display:block}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item{font-size:0;visibility:hidden}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested{display:none;font-size:.7rem;overflow:auto;visibility:visible}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--nested>.md-nav__link{display:none}.md-tabs--active~.md-main .md-nav--primary>.md-nav__list>.md-nav__item--active{display:block}.md-tabs--active~.md-main .md-nav[data-md-level="1"]{max-height:none;overflow:visible}.md-tabs--active~.md-main .md-nav[data-md-level="1"]>.md-nav__list>.md-nav__item{padding-left:0}.md-tabs--active~.md-main .md-nav[data-md-level="1"] .md-nav .md-nav__title{display:none}}@media only screen and (min-width:45em){.md-footer-nav__link{width:50%}.md-footer-copyright{max-width:75%;float:left}[dir=rtl] .md-footer-copyright{float:right}.md-footer-social{padding:.6rem 0;float:right}[dir=rtl] .md-footer-social{float:left}}@media only screen and (max-width:29.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(45);transform:scale(45)}}@media only screen and (min-width:30em) and (max-width:44.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(60);transform:scale(60)}}@media only screen and (min-width:45em) and (max-width:59.9375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{-webkit-transform:scale(75);transform:scale(75)}}@media only screen and (min-width:60em) and (max-width:76.1875em){.md-search__scrollwrap,[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}.md-search-result__teaser{max-height:2.5rem;-webkit-line-clamp:3}} \ No newline at end of file diff --git a/attachments/DJI-HttpRunner.pdf b/attachments/DJI-HttpRunner.pdf new file mode 100644 index 00000000..d73dcf5c Binary files /dev/null and b/attachments/DJI-HttpRunner.pdf differ diff --git a/attachments/MTSC2019-HttpRunner-2.0.pdf b/attachments/MTSC2019-HttpRunner-2.0.pdf new file mode 100644 index 00000000..a25e8caf Binary files /dev/null and b/attachments/MTSC2019-HttpRunner-2.0.pdf differ diff --git a/attachments/PyCon-HttpRunner.pdf b/attachments/PyCon-HttpRunner.pdf new file mode 100644 index 00000000..a5073f79 Binary files /dev/null and b/attachments/PyCon-HttpRunner.pdf differ diff --git a/concept/nominal/index.html b/concept/nominal/index.html new file mode 100644 index 00000000..3d1e2b00 --- /dev/null +++ b/concept/nominal/index.html @@ -0,0 +1,917 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 名词解释 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+ +
+ + +
+
+ + + +

名词解释

+ +

测试用例(testcase)

+

从 2.0 版本开始,HttpRunner 开始对测试用例的定义进行进一步的明确,参考 wiki 上的描述。

+
+

A test case is a specification of the inputs, execution conditions, testing procedure, and expected results that define a single test to be executed to achieve a particular software testing objective, such as to exercise a particular program path or to verify compliance with a specific requirement.

+
+

概括下来,一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:

+
    +
  • 明确的测试目的(achieve a particular software testing objective)
  • +
  • 明确的输入(inputs)
  • +
  • 明确的运行环境(execution conditions)
  • +
  • 明确的测试步骤描述(testing procedure)
  • +
  • 明确的预期结果(expected results)
  • +
+

对应地,HttpRunner 的测试用例描述方式进行如下设计:

+
    +
  • 测试用例应该是完整且独立的,每条测试用例应该是都可以独立运行的;在 HttpRunner 中,每个 YAML/JSON 文件对应一条测试用例。
  • +
  • 测试用例包含 测试脚本测试数据 两部分:
      +
    • 测试用例 = 测试脚本 + 测试数据
    • +
    • 测试脚本 重点是描述测试的 业务功能逻辑,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.py)实现复杂的运算逻辑;可以将 测试脚本 理解为编程语言中的 类(class)
    • +
    • 测试数据 重点是对应测试的 业务数据逻辑,可以理解为类的实例化数据;
    • +
    • 测试数据测试脚本 分离后,就可以比较方便地实现数据驱动测试,通过对测试脚本传入一组数据,实现同一业务功能在不同数据逻辑下的测试验证。
    • +
    +
  • +
+

测试步骤(teststep)

+

测试用例是测试步骤的 有序 集合,而对于接口测试来说,每一个测试步骤应该就对应一个 API 的请求描述。

+

测试用例集(testsuite)

+

测试用例集测试用例无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的。

+

如果确实存在先后依赖关系怎么办,例如登录功能和下单功能。正确的做法应该是,在下单测试用例的前置步骤中执行登录操作。

+
- config:
+    name: order product
+
+- test:
+    name: login
+    testcase: testcases/login.yml
+
+- test:
+    name: add to cart
+    api: api/add_cart.yml
+
+- test:
+    name: make order
+    api: api/make_order.yml
+
+ + +

测试场景

+

测试场景测试用例集 是同一概念,都是 测试用例无序 集合。

+
    +
  • 接口
  • +
  • 测试用例集
  • +
  • 参数
  • +
  • 变量
  • +
  • 测试脚本(YAML/JSON)
  • +
  • debugtalk.py
  • +
  • 环境变量
  • +
+

项目根目录

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/__pycache__/api_server.cpython-36.pyc b/data/__pycache__/api_server.cpython-36.pyc new file mode 100644 index 00000000..da3ca121 Binary files /dev/null and b/data/__pycache__/api_server.cpython-36.pyc differ diff --git a/data/__pycache__/api_server.cpython-37.pyc b/data/__pycache__/api_server.cpython-37.pyc new file mode 100644 index 00000000..1a816867 Binary files /dev/null and b/data/__pycache__/api_server.cpython-37.pyc differ diff --git a/data/__pycache__/debugtalk.cpython-36.pyc b/data/__pycache__/debugtalk.cpython-36.pyc new file mode 100644 index 00000000..1ccacb7e Binary files /dev/null and b/data/__pycache__/debugtalk.cpython-36.pyc differ diff --git a/data/__pycache__/debugtalk.cpython-37.pyc b/data/__pycache__/debugtalk.cpython-37.pyc new file mode 100644 index 00000000..ab7f8fa3 Binary files /dev/null and b/data/__pycache__/debugtalk.cpython-37.pyc differ diff --git a/data/account.csv b/data/account.csv new file mode 100644 index 00000000..2502b5b8 --- /dev/null +++ b/data/account.csv @@ -0,0 +1,4 @@ +username,password,phone +test1,111111,18600000001 +test2,222222,18600000002 +test3,333333,18600000003 \ No newline at end of file diff --git a/data/api_server.py b/data/api_server.py new file mode 100644 index 00000000..bff04ddf --- /dev/null +++ b/data/api_server.py @@ -0,0 +1,223 @@ +import hashlib +import hmac +import json +import random +import string +from functools import wraps + +from flask import Flask, make_response, request + +SECRET_KEY = "DebugTalk" + +app = Flask(__name__) + +""" storage all users' data +data structure: + users_dict = { + 'uid1': { + 'name': 'name1', + 'password': 'pwd1' + }, + 'uid2': { + 'name': 'name2', + 'password': 'pwd2' + } + } +""" +users_dict = {} + +""" storage all token data +data structure: + token_dict = { + 'device_sn1': 'token1', + 'device_sn2': 'token1' + } +""" +token_dict = {} + + +def gen_random_string(str_len): + """ generate random string with specified length + """ + return ''.join( + random.choice(string.ascii_letters + string.digits) for _ in range(str_len)) + +def get_sign(*args): + content = ''.join(args).encode('ascii') + sign_key = SECRET_KEY.encode('ascii') + sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest() + return sign + +def gen_md5(*args): + return hashlib.md5("".join(args).encode('utf-8')).hexdigest() + + +def validate_request(func): + + @wraps(func) + def wrapper(*args, **kwargs): + device_sn = request.headers.get('device_sn', "") + token = request.headers.get('token', "") + + if not device_sn or not token: + result = { + 'success': False, + 'msg': "device_sn or token is null." + } + response = make_response(json.dumps(result), 401) + response.headers["Content-Type"] = "application/json" + return response + + if token_dict.get(device_sn) != token: + result = { + 'success': False, + 'msg': "Authorization failed!" + } + response = make_response(json.dumps(result), 403) + response.headers["Content-Type"] = "application/json" + return response + + return func(*args, **kwargs) + + return wrapper + + +@app.route('/') +def index(): + return "Hello World!" + +@app.route('/api/get-token', methods=['POST']) +def get_token(): + device_sn = request.headers.get('device_sn', "") + os_platform = request.headers.get('os_platform', "") + app_version = request.headers.get('app_version', "") + data = request.get_json() + sign = data.get('sign', "") + + expected_sign = get_sign(device_sn, os_platform, app_version) + + if expected_sign != sign: + result = { + 'success': False, + 'msg': "Authorization failed!" + } + response = make_response(json.dumps(result), 403) + else: + token = gen_random_string(16) + token_dict[device_sn] = token + + result = { + 'success': True, + 'token': token + } + response = make_response(json.dumps(result)) + + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/users') +@validate_request +def get_users(): + users_list = [user for uid, user in users_dict.items()] + users = { + 'success': True, + 'count': len(users_list), + 'items': users_list + } + response = make_response(json.dumps(users)) + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/reset-all') +@validate_request +def clear_users(): + users_dict.clear() + result = { + 'success': True + } + response = make_response(json.dumps(result)) + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/users/', methods=['POST']) +@validate_request +def create_user(uid): + user = request.get_json() + if uid not in users_dict: + result = { + 'success': True, + 'msg': "user created successfully." + } + status_code = 201 + users_dict[uid] = user + else: + result = { + 'success': False, + 'msg': "user already existed." + } + status_code = 500 + + response = make_response(json.dumps(result), status_code) + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/users/') +@validate_request +def get_user(uid): + user = users_dict.get(uid, {}) + if user: + result = { + 'success': True, + 'data': user + } + status_code = 200 + else: + result = { + 'success': False, + 'data': user + } + status_code = 404 + + response = make_response(json.dumps(result), status_code) + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/users/', methods=['PUT']) +@validate_request +def update_user(uid): + user = users_dict.get(uid, {}) + if user: + user = request.get_json() + success = True + status_code = 200 + users_dict[uid] = user + else: + success = False + status_code = 404 + + result = { + 'success': success, + 'data': user + } + response = make_response(json.dumps(result), status_code) + response.headers["Content-Type"] = "application/json" + return response + +@app.route('/api/users/', methods=['DELETE']) +@validate_request +def delete_user(uid): + user = users_dict.pop(uid, {}) + if user: + success = True + status_code = 200 + else: + success = False + status_code = 404 + + result = { + 'success': success, + 'data': user + } + response = make_response(json.dumps(result), status_code) + response.headers["Content-Type"] = "application/json" + return response diff --git a/data/app_version.csv b/data/app_version.csv new file mode 100644 index 00000000..a0c236a4 --- /dev/null +++ b/data/app_version.csv @@ -0,0 +1,3 @@ +app_version +2.8.5 +2.8.6 diff --git a/data/debugtalk.py b/data/debugtalk.py new file mode 100644 index 00000000..53b56d02 --- /dev/null +++ b/data/debugtalk.py @@ -0,0 +1,48 @@ +import hashlib +import hmac +import random +import string +import time + +SECRET_KEY = "DebugTalk" + +def gen_random_string(str_len): + random_char_list = [] + for _ in range(str_len): + random_char = random.choice(string.ascii_letters + string.digits) + random_char_list.append(random_char) + + random_string = ''.join(random_char_list) + return random_string + +def get_sign(*args): + content = ''.join(args).encode('ascii') + sign_key = SECRET_KEY.encode('ascii') + sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest() + return sign + +def gen_user_id(): + return int(time.time() * 1000) + +def get_user_id(): + return [ + {"user_id": 1001}, + {"user_id": 1002}, + {"user_id": 1003}, + {"user_id": 1004} + ] + +def get_account(num): + accounts = [] + for index in range(1, num+1): + accounts.append( + {"username": "user%s" % index, "password": str(index) * 6}, + ) + + return accounts + +def get_os_platform(): + return [ + {"os_platform": "ios"}, + {"os_platform": "android"} + ] diff --git a/data/demo-parameters-get-token.yml b/data/demo-parameters-get-token.yml new file mode 100644 index 00000000..d1bfb633 --- /dev/null +++ b/data/demo-parameters-get-token.yml @@ -0,0 +1,10 @@ +config: + name: get token with parameters + +testcases: + get token with $user_agent, $app_version, $os_platform: + testcase: demo-testcase-get-token.yml + parameters: + user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"] + app_version: ${P(app_version.csv)} + os_platform: ${get_os_platform()} diff --git a/data/demo-quickstart-0.json b/data/demo-quickstart-0.json new file mode 100644 index 00000000..57dd4ce9 --- /dev/null +++ b/data/demo-quickstart-0.json @@ -0,0 +1,58 @@ +[ + { + "config": { + "name": "testcase description", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "request": { + "url": "http://127.0.0.1:5000/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.token", "baNLX1zhFYP11Seb"]} + ] + } + }, + { + "test": { + "name": "/api/users/1000", + "request": { + "url": "http://127.0.0.1:5000/api/users/1000", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "token": "baNLX1zhFYP11Seb", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart-0.yml b/data/demo-quickstart-0.yml new file mode 100644 index 00000000..9e50c8b6 --- /dev/null +++ b/data/demo-quickstart-0.yml @@ -0,0 +1,41 @@ +- config: + name: testcase description + variables: {} + +- test: + name: /api/get-token + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: http://127.0.0.1:5000/api/get-token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.token, baNLX1zhFYP11Seb] + +- test: + name: /api/users/1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: FwgRiO7CNA50DSU + token: baNLX1zhFYP11Seb + json: + name: user1 + password: '123456' + method: POST + url: http://127.0.0.1:5000/api/users/1000 + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] \ No newline at end of file diff --git a/data/demo-quickstart-1.json b/data/demo-quickstart-1.json new file mode 100644 index 00000000..433666c8 --- /dev/null +++ b/data/demo-quickstart-1.json @@ -0,0 +1,57 @@ +[ + { + "config": { + "name": "testcase description", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "request": { + "url": "http://127.0.0.1:5000/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/1000", + "request": { + "url": "http://127.0.0.1:5000/api/users/1000", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "token": "baNLX1zhFYP11Seb", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart-1.yml b/data/demo-quickstart-1.yml new file mode 100644 index 00000000..1874c75d --- /dev/null +++ b/data/demo-quickstart-1.yml @@ -0,0 +1,40 @@ +- config: + name: testcase description + variables: {} + +- test: + name: /api/get-token + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: http://127.0.0.1:5000/api/get-token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: FwgRiO7CNA50DSU + token: baNLX1zhFYP11Seb + json: + name: user1 + password: '123456' + method: POST + url: http://127.0.0.1:5000/api/users/1000 + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] \ No newline at end of file diff --git a/data/demo-quickstart-2.json b/data/demo-quickstart-2.json new file mode 100644 index 00000000..6c24da28 --- /dev/null +++ b/data/demo-quickstart-2.json @@ -0,0 +1,60 @@ +[ + { + "config": { + "name": "testcase description", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "request": { + "url": "http://127.0.0.1:5000/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "extract": [ + {"token": "content.token"} + ], + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/1000", + "request": { + "url": "http://127.0.0.1:5000/api/users/1000", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "token": "$token", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] diff --git a/data/demo-quickstart-2.yml b/data/demo-quickstart-2.yml new file mode 100644 index 00000000..17924f0d --- /dev/null +++ b/data/demo-quickstart-2.yml @@ -0,0 +1,42 @@ +- config: + name: testcase description + variables: {} + +- test: + name: /api/get-token + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: http://127.0.0.1:5000/api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: FwgRiO7CNA50DSU + token: $token + json: + name: user1 + password: '123456' + method: POST + url: http://127.0.0.1:5000/api/users/1000 + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] diff --git a/data/demo-quickstart-3.json b/data/demo-quickstart-3.json new file mode 100644 index 00000000..fd276ca7 --- /dev/null +++ b/data/demo-quickstart-3.json @@ -0,0 +1,61 @@ +[ + { + "config": { + "name": "testcase description", + "base_url": "http://127.0.0.1:5000", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "request": { + "url": "/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "extract": [ + {"token": "content.token"} + ], + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/1000", + "request": { + "url": "/api/users/1000", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "token": "$token", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] diff --git a/data/demo-quickstart-3.yml b/data/demo-quickstart-3.yml new file mode 100644 index 00000000..654a383b --- /dev/null +++ b/data/demo-quickstart-3.yml @@ -0,0 +1,43 @@ +- config: + name: testcase description + base_url: http://127.0.0.1:5000 + variables: {} + +- test: + name: /api/get-token + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: /api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: FwgRiO7CNA50DSU + token: $token + json: + name: user1 + password: '123456' + method: POST + url: /api/users/1000 + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] diff --git a/data/demo-quickstart-4.json b/data/demo-quickstart-4.json new file mode 100644 index 00000000..ef03348e --- /dev/null +++ b/data/demo-quickstart-4.json @@ -0,0 +1,70 @@ +[ + { + "config": { + "name": "testcase description", + "base_url": "http://127.0.0.1:5000", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "variables": { + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6" + }, + "request": { + "url": "/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "os_platform": "$os_platform", + "app_version": "$app_version", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "extract": [ + {"token": "content.token"} + ], + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/$user_id", + "variables": { + "device_sn": "FwgRiO7CNA50DSU", + "user_id": "1000" + }, + "request": { + "url": "/api/users/$user_id", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "token": "$token", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart-4.yml b/data/demo-quickstart-4.yml new file mode 100644 index 00000000..514b25f6 --- /dev/null +++ b/data/demo-quickstart-4.yml @@ -0,0 +1,50 @@ +- config: + name: testcase description + base_url: http://127.0.0.1:5000 + variables: {} + +- test: + name: /api/get-token + variables: + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: $app_version + device_sn: $device_sn + os_platform: $os_platform + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: /api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/$user_id + variables: + device_sn: FwgRiO7CNA50DSU + user_id: 1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: $device_sn + token: $token + json: + name: user1 + password: '123456' + method: POST + url: /api/users/$user_id + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] diff --git a/data/demo-quickstart-5.json b/data/demo-quickstart-5.json new file mode 100644 index 00000000..4ede41f7 --- /dev/null +++ b/data/demo-quickstart-5.json @@ -0,0 +1,70 @@ +[ + { + "config": { + "name": "testcase description", + "base_url": "http://127.0.0.1:5000", + "variables": { + "device_sn": "FwgRiO7CNA50DSU" + } + } + }, + { + "test": { + "name": "/api/get-token", + "variables": { + "os_platform": "ios", + "app_version": "2.8.6" + }, + "request": { + "url": "/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "os_platform": "$os_platform", + "app_version": "$app_version", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "extract": [ + {"token": "content.token"} + ], + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/$user_id", + "variables": { + "user_id": "1000" + }, + "request": { + "url": "/api/users/$user_id", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "token": "$token", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart-5.yml b/data/demo-quickstart-5.yml new file mode 100644 index 00000000..98445b7f --- /dev/null +++ b/data/demo-quickstart-5.yml @@ -0,0 +1,49 @@ +- config: + name: testcase description + base_url: http://127.0.0.1:5000 + variables: + device_sn: FwgRiO7CNA50DSU + +- test: + name: /api/get-token + variables: + app_version: 2.8.6 + os_platform: ios + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: $app_version + device_sn: $device_sn + os_platform: $os_platform + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: /api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/$user_id + variables: + user_id: 1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: $device_sn + token: $token + json: + name: user1 + password: '123456' + method: POST + url: /api/users/$user_id + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] diff --git a/data/demo-quickstart-6.json b/data/demo-quickstart-6.json new file mode 100644 index 00000000..da126fca --- /dev/null +++ b/data/demo-quickstart-6.json @@ -0,0 +1,70 @@ +[ + { + "config": { + "name": "testcase description", + "base_url": "http://127.0.0.1:5000", + "variables": { + "device_sn": "${gen_random_string(15)}" + } + } + }, + { + "test": { + "name": "/api/get-token", + "variables": { + "os_platform": "ios", + "app_version": "2.8.6" + }, + "request": { + "url": "/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "os_platform": "$os_platform", + "app_version": "$app_version", + "Content-Type": "application/json" + }, + "json": { + "sign": "${get_sign($device_sn, $os_platform, $app_version)}" + } + }, + "extract": [ + {"token": "content.token"} + ], + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]} + ] + } + }, + { + "test": { + "name": "/api/users/$user_id", + "variables": { + "user_id": "${gen_user_id()}" + }, + "request": { + "url": "/api/users/$user_id", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "$device_sn", + "token": "$token", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + {"eq": ["status_code", 201]}, + {"eq": ["headers.Content-Type", "application/json"]}, + {"eq": ["content.success", true]}, + {"eq": ["content.msg", "user created successfully."]} + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart-6.yml b/data/demo-quickstart-6.yml new file mode 100644 index 00000000..b5acf937 --- /dev/null +++ b/data/demo-quickstart-6.yml @@ -0,0 +1,49 @@ +- config: + name: testcase description + base_url: http://127.0.0.1:5000 + variables: + device_sn: ${gen_random_string(15)} + +- test: + name: /api/get-token + variables: + app_version: 2.8.6 + os_platform: ios + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: $app_version + device_sn: $device_sn + os_platform: $os_platform + json: + sign: ${get_sign($device_sn, $os_platform, $app_version)} + method: POST + url: /api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + +- test: + name: /api/users/$user_id + variables: + user_id: ${gen_user_id()} + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: $device_sn + token: $token + json: + name: user1 + password: '123456' + method: POST + url: /api/users/$user_id + validate: + - eq: [status_code, 201] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] + - eq: [content.msg, user created successfully.] diff --git a/data/demo-quickstart-7.json b/data/demo-quickstart-7.json new file mode 100644 index 00000000..89d57f8d --- /dev/null +++ b/data/demo-quickstart-7.json @@ -0,0 +1,13 @@ +{ + "config": { + "name": "create users with parameters" + }, + "testcases": { + "create user $user_id": { + "testcase": "demo-quickstart-6.yml", + "parameters": { + "user_id": [1001, 1002, 1003, 1004] + } + } + } +} \ No newline at end of file diff --git a/data/demo-quickstart-7.yml b/data/demo-quickstart-7.yml new file mode 100644 index 00000000..770dc45f --- /dev/null +++ b/data/demo-quickstart-7.yml @@ -0,0 +1,8 @@ +config: + name: testcase description + +testcases: + create user $user_id: + testcase: demo-quickstart-6.yml + parameters: + user_id: [1001, 1002, 1003, 1004] diff --git a/data/demo-quickstart.har b/data/demo-quickstart.har new file mode 100644 index 00000000..d4924ba4 --- /dev/null +++ b/data/demo-quickstart.har @@ -0,0 +1,221 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Charles Proxy", + "version": "4.2.1" + }, + "entries": [ + { + "startedDateTime": "2018-02-19T17:30:00.904+08:00", + "time": 3, + "request": { + "method": "POST", + "url": "http://127.0.0.1:5000/api/get-token", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Host", + "value": "127.0.0.1:5000" + }, + { + "name": "User-Agent", + "value": "python-requests/2.18.4" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate" + }, + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "device_sn", + "value": "FwgRiO7CNA50DSU" + }, + { + "name": "os_platform", + "value": "ios" + }, + { + "name": "app_version", + "value": "2.8.6" + }, + { + "name": "Content-Length", + "value": "52" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"sign\": \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\"}" + }, + "headersSize": 299, + "bodySize": 52 + }, + "response": { + "_charlesStatus": "COMPLETE", + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.0", + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Content-Length", + "value": "46" + }, + { + "name": "Server", + "value": "Werkzeug/0.14.1 Python/3.6.4" + }, + { + "name": "Date", + "value": "Mon, 19 Feb 2018 09:30:00 GMT" + }, + { + "name": "Proxy-Connection", + "value": "Close" + } + ], + "content": { + "size": 46, + "mimeType": "application/json", + "text": "eyJzdWNjZXNzIjogdHJ1ZSwgInRva2VuIjogImJhTkxYMXpoRllQMTFTZWIifQ==", + "encoding": "base64" + }, + "redirectURL": null, + "headersSize": 175, + "bodySize": 46 + }, + "serverIPAddress": "127.0.0.1", + "cache": {}, + "timings": { + "dns": 1, + "connect": 0, + "ssl": -1, + "send": 0, + "wait": 1, + "receive": 1 + } + }, + { + "startedDateTime": "2018-02-19T17:30:00.911+08:00", + "time": 3, + "request": { + "method": "POST", + "url": "http://127.0.0.1:5000/api/users/1000", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Host", + "value": "127.0.0.1:5000" + }, + { + "name": "User-Agent", + "value": "python-requests/2.18.4" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate" + }, + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "device_sn", + "value": "FwgRiO7CNA50DSU" + }, + { + "name": "token", + "value": "baNLX1zhFYP11Seb" + }, + { + "name": "Content-Length", + "value": "39" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ], + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"name\": \"user1\", \"password\": \"123456\"}" + }, + "headersSize": 265, + "bodySize": 39 + }, + "response": { + "_charlesStatus": "COMPLETE", + "status": 201, + "statusText": "CREATED", + "httpVersion": "HTTP/1.0", + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Content-Length", + "value": "54" + }, + { + "name": "Server", + "value": "Werkzeug/0.14.1 Python/3.6.4" + }, + { + "name": "Date", + "value": "Mon, 19 Feb 2018 09:30:00 GMT" + }, + { + "name": "Proxy-Connection", + "value": "Close" + } + ], + "content": { + "size": 54, + "mimeType": "application/json", + "text": "eyJzdWNjZXNzIjogdHJ1ZSwgIm1zZyI6ICJ1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5LiJ9", + "encoding": "base64" + }, + "redirectURL": null, + "headersSize": 77, + "bodySize": 54 + }, + "serverIPAddress": "127.0.0.1", + "cache": {}, + "timings": { + "dns": 0, + "connect": 0, + "ssl": -1, + "send": 0, + "wait": 3, + "receive": 0 + } + } + ] + } +} \ No newline at end of file diff --git a/data/demo-quickstart.json b/data/demo-quickstart.json new file mode 100644 index 00000000..88933729 --- /dev/null +++ b/data/demo-quickstart.json @@ -0,0 +1,98 @@ +[ + { + "config": { + "name": "testcase description", + "variables": {} + } + }, + { + "test": { + "name": "/api/get-token", + "request": { + "url": "http://127.0.0.1:5000/api/get-token", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "os_platform": "ios", + "app_version": "2.8.6", + "Content-Type": "application/json" + }, + "json": { + "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98" + } + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq": [ + "content.success", + true + ] + }, + { + "eq": [ + "content.token", + "baNLX1zhFYP11Seb" + ] + } + ] + } + }, + { + "test": { + "name": "/api/users/1000", + "request": { + "url": "http://127.0.0.1:5000/api/users/1000", + "method": "POST", + "headers": { + "User-Agent": "python-requests/2.18.4", + "device_sn": "FwgRiO7CNA50DSU", + "token": "baNLX1zhFYP11Seb", + "Content-Type": "application/json" + }, + "json": { + "name": "user1", + "password": "123456" + } + }, + "validate": [ + { + "eq": [ + "status_code", + 201 + ] + }, + { + "eq": [ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq": [ + "content.success", + true + ] + }, + { + "eq": [ + "content.msg", + "user created successfully." + ] + } + ] + } + } +] \ No newline at end of file diff --git a/data/demo-quickstart.yml b/data/demo-quickstart.yml new file mode 100644 index 00000000..6eb2f64c --- /dev/null +++ b/data/demo-quickstart.yml @@ -0,0 +1,55 @@ +- config: + name: testcase description + variables: {} +- test: + name: /api/get-token + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: 2.8.6 + device_sn: FwgRiO7CNA50DSU + os_platform: ios + json: + sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 + method: POST + url: http://127.0.0.1:5000/api/get-token + validate: + - eq: + - status_code + - 200 + - eq: + - headers.Content-Type + - application/json + - eq: + - content.success + - true + - eq: + - content.token + - baNLX1zhFYP11Seb +- test: + name: /api/users/1000 + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + device_sn: FwgRiO7CNA50DSU + token: baNLX1zhFYP11Seb + json: + name: user1 + password: '123456' + method: POST + url: http://127.0.0.1:5000/api/users/1000 + validate: + - eq: + - status_code + - 201 + - eq: + - headers.Content-Type + - application/json + - eq: + - content.success + - true + - eq: + - content.msg + - user created successfully. diff --git a/data/demo-testcase-get-token.yml b/data/demo-testcase-get-token.yml new file mode 100644 index 00000000..32db8d1e --- /dev/null +++ b/data/demo-testcase-get-token.yml @@ -0,0 +1,27 @@ +- config: + name: get token + base_url: http://127.0.0.1:5000 + variables: + device_sn: ${gen_random_string(15)} + os_platform: 'ios' + app_version: '2.8.6' + +- test: + name: get token with $device_sn, $os_platform, $app_version + request: + headers: + Content-Type: application/json + User-Agent: python-requests/2.18.4 + app_version: $app_version + device_sn: $device_sn + os_platform: $os_platform + json: + sign: ${get_sign($device_sn, $os_platform, $app_version)} + method: POST + url: /api/get-token + extract: + token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] diff --git a/data/demo_parameters.yml b/data/demo_parameters.yml new file mode 100644 index 00000000..76a8a4b9 --- /dev/null +++ b/data/demo_parameters.yml @@ -0,0 +1,34 @@ +- config: + name: "user management testcase." + parameters: + - user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"] + - app_version: ${P(app_version.csv)} + - os_platform: ${get_os_platform()} + variables: + - user_agent: 'iOS/10.3' + - device_sn: ${gen_random_string(15)} + - os_platform: 'ios' + - app_version: '2.8.6' + request: + base_url: http://127.0.0.1:5000 + headers: + Content-Type: application/json + device_sn: $device_sn + +- test: + name: get token with $user_agent, $os_platform, $app_version + request: + url: /api/get-token + method: POST + headers: + app_version: $app_version + os_platform: $os_platform + user_agent: $user_agent + json: + sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)} + extract: + - token: content.token + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, application/json] + - eq: [content.success, true] \ No newline at end of file diff --git a/data/logs/demo-quickstart-6.loaded.json b/data/logs/demo-quickstart-6.loaded.json new file mode 100644 index 00000000..1d6bf7e1 --- /dev/null +++ b/data/logs/demo-quickstart-6.loaded.json @@ -0,0 +1,115 @@ +{ + "project_mapping":{ + "env":{}, + "PWD":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data", + "debugtalk.py":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/debugtalk.py", + "test_path":"docs/data/demo-quickstart-6.json" + }, + "testcases":[ + { + "config":{ + "name":"testcase description", + "base_url":"http://127.0.0.1:5000", + "variables":{ + "device_sn":"${gen_random_string(15)}" + } + }, + "teststeps":[ + { + "name":"/api/get-token", + "variables":{ + "os_platform":"ios", + "app_version":"2.8.6" + }, + "request":{ + "url":"/api/get-token", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "device_sn":"$device_sn", + "os_platform":"$os_platform", + "app_version":"$app_version", + "Content-Type":"application/json" + }, + "json":{ + "sign":"${get_sign($device_sn, $os_platform, $app_version)}" + } + }, + "extract":[ + { + "token":"content.token" + } + ], + "validate":[ + { + "eq":[ + "status_code", + 200 + ] + }, + { + "eq":[ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq":[ + "content.success", + true + ] + } + ] + }, + { + "name":"/api/users/$user_id", + "variables":{ + "user_id":"${gen_user_id()}" + }, + "request":{ + "url":"/api/users/$user_id", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "device_sn":"$device_sn", + "token":"$token", + "Content-Type":"application/json" + }, + "json":{ + "name":"user1", + "password":"123456" + } + }, + "validate":[ + { + "eq":[ + "status_code", + 201 + ] + }, + { + "eq":[ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq":[ + "content.success", + true + ] + }, + { + "eq":[ + "content.msg", + "user created successfully." + ] + } + ] + } + ], + "path":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/demo-quickstart-6.json", + "type":"testcase" + } + ] +} \ No newline at end of file diff --git a/data/logs/demo-quickstart-6.parsed.json b/data/logs/demo-quickstart-6.parsed.json new file mode 100644 index 00000000..f3553251 --- /dev/null +++ b/data/logs/demo-quickstart-6.parsed.json @@ -0,0 +1,115 @@ +{ + "project_mapping":{ + "env":{}, + "PWD":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data", + "debugtalk.py":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/debugtalk.py", + "test_path":"docs/data/demo-quickstart-6.json" + }, + "testcases":[ + { + "config":{ + "name":"testcase description" + }, + "teststeps":[ + { + "request":{ + "url":"http://127.0.0.1:5000/api/get-token", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "device_sn":"$device_sn", + "os_platform":"$os_platform", + "app_version":"$app_version", + "Content-Type":"application/json" + }, + "json":{ + "sign":"${get_sign($device_sn, $os_platform, $app_version)}" + } + }, + "extract":[ + { + "token":"content.token" + } + ], + "validate":[ + { + "eq":[ + "status_code", + 200 + ] + }, + { + "eq":[ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq":[ + "content.success", + true + ] + } + ], + "verify":true, + "variables":{ + "os_platform":"ios", + "app_version":"2.8.6", + "device_sn":"mmHBimjzK4Pk6mg" + }, + "name":"/api/get-token" + }, + { + "request":{ + "url":"http://127.0.0.1:5000/api/users/1548560768589", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "device_sn":"$device_sn", + "token":"$token", + "Content-Type":"application/json" + }, + "json":{ + "name":"user1", + "password":"123456" + } + }, + "validate":[ + { + "eq":[ + "status_code", + 201 + ] + }, + { + "eq":[ + "headers.Content-Type", + "application/json" + ] + }, + { + "eq":[ + "content.success", + true + ] + }, + { + "eq":[ + "content.msg", + "user created successfully." + ] + } + ], + "verify":true, + "variables":{ + "user_id":1548560768589, + "device_sn":"mmHBimjzK4Pk6mg" + }, + "name":"/api/users/1548560768589" + } + ], + "path":"/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/demo-quickstart-6.json", + "type":"testcase" + } + ] +} \ No newline at end of file diff --git a/data/logs/demo-quickstart-6.summary.json b/data/logs/demo-quickstart-6.summary.json new file mode 100644 index 00000000..3fb63701 --- /dev/null +++ b/data/logs/demo-quickstart-6.summary.json @@ -0,0 +1,361 @@ +{ + "success":true, + "stat":{ + "testcases":{ + "total":1, + "success":1, + "fail":0 + }, + "teststeps":{ + "total":2, + "failures":0, + "errors":0, + "skipped":0, + "expectedFailures":0, + "unexpectedSuccesses":0, + "successes":2 + } + }, + "time":{ + "start_at":1548560768.590071, + "duration":0.036631107330322266 + }, + "platform":{ + "httprunner_version":"2.0.2", + "python_version":"CPython 3.7.0", + "platform":"Darwin-18.2.0-x86_64-i386-64bit" + }, + "details":[ + { + "success":true, + "stat":{ + "total":2, + "failures":0, + "errors":0, + "skipped":0, + "expectedFailures":0, + "unexpectedSuccesses":0, + "successes":2 + }, + "time":{ + "start_at":1548560768.590071, + "duration":0.036631107330322266 + }, + "records":[ + { + "name":"/api/get-token", + "status":"success", + "attachment":"", + "meta_datas":{ + "name":"/api/get-token", + "data":[ + { + "request":{ + "url":"http://127.0.0.1:5000/api/get-token", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "Accept-Encoding":"gzip, deflate", + "Accept":"*/*", + "Connection":"keep-alive", + "device_sn":"mmHBimjzK4Pk6mg", + "os_platform":"ios", + "app_version":"2.8.6", + "Content-Type":"application/json", + "Content-Length":"52" + }, + "body":"{"sign": "57885427ba4f3d55b6bce51827c5152a0f0f850f"}" + }, + "response":{ + "ok":true, + "url":"http://127.0.0.1:5000/api/get-token", + "status_code":200, + "reason":"OK", + "cookies":{}, + "encoding":"None", + "headers":{ + "Content-Type":"application/json", + "Content-Length":"46", + "Server":"Werkzeug/0.14.1 Python/3.6.5+", + "Date":"Sun, 27 Jan 2019 03:46:08 GMT" + }, + "content_type":"application/json", + "json":{ + "success":true, + "token":"C3olNHYCMWfSQkCW" + } + } + } + ], + "stat":{ + "response_time_ms":11.42, + "elapsed_ms":3.71, + "content_size":46 + }, + "validators":[ + { + "check":"status_code", + "expect":200, + "comparator":"eq", + "check_value":200, + "check_result":"pass" + }, + { + "check":"headers.Content-Type", + "expect":"application/json", + "comparator":"eq", + "check_value":"application/json", + "check_result":"pass" + }, + { + "check":"content.success", + "expect":true, + "comparator":"eq", + "check_value":true, + "check_result":"pass" + } + ] + }, + "meta_datas_expanded":[ + { + "name":"/api/get-token", + "data":[ + { + "request":{ + "url":"http://127.0.0.1:5000/api/get-token", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "Accept-Encoding":"gzip, deflate", + "Accept":"*/*", + "Connection":"keep-alive", + "device_sn":"mmHBimjzK4Pk6mg", + "os_platform":"ios", + "app_version":"2.8.6", + "Content-Type":"application/json", + "Content-Length":"52" + }, + "body":"{"sign": "57885427ba4f3d55b6bce51827c5152a0f0f850f"}" + }, + "response":{ + "ok":true, + "url":"http://127.0.0.1:5000/api/get-token", + "status_code":200, + "reason":"OK", + "cookies":{}, + "encoding":"None", + "headers":{ + "Content-Type":"application/json", + "Content-Length":"46", + "Server":"Werkzeug/0.14.1 Python/3.6.5+", + "Date":"Sun, 27 Jan 2019 03:46:08 GMT" + }, + "content_type":"application/json", + "json":{ + "success":true, + "token":"C3olNHYCMWfSQkCW" + } + } + } + ], + "stat":{ + "response_time_ms":11.42, + "elapsed_ms":3.71, + "content_size":46 + }, + "validators":[ + { + "check":"status_code", + "expect":200, + "comparator":"eq", + "check_value":200, + "check_result":"pass" + }, + { + "check":"headers.Content-Type", + "expect":"application/json", + "comparator":"eq", + "check_value":"application/json", + "check_result":"pass" + }, + { + "check":"content.success", + "expect":true, + "comparator":"eq", + "check_value":true, + "check_result":"pass" + } + ] + } + ], + "response_time":"11.42" + }, + { + "name":"/api/users/1548560768589", + "status":"success", + "attachment":"", + "meta_datas":{ + "name":"/api/users/1548560768589", + "data":[ + { + "request":{ + "url":"http://127.0.0.1:5000/api/users/1548560768589", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "Accept-Encoding":"gzip, deflate", + "Accept":"*/*", + "Connection":"keep-alive", + "device_sn":"mmHBimjzK4Pk6mg", + "token":"C3olNHYCMWfSQkCW", + "Content-Type":"application/json", + "Content-Length":"39" + }, + "body":"{"name": "user1", "password": "123456"}" + }, + "response":{ + "ok":true, + "url":"http://127.0.0.1:5000/api/users/1548560768589", + "status_code":201, + "reason":"CREATED", + "cookies":{}, + "encoding":"None", + "headers":{ + "Content-Type":"application/json", + "Content-Length":"54", + "Server":"Werkzeug/0.14.1 Python/3.6.5+", + "Date":"Sun, 27 Jan 2019 03:46:08 GMT" + }, + "content_type":"application/json", + "json":{ + "success":true, + "msg":"user created successfully." + } + } + } + ], + "stat":{ + "response_time_ms":2.8, + "elapsed_ms":1.748, + "content_size":54 + }, + "validators":[ + { + "check":"status_code", + "expect":201, + "comparator":"eq", + "check_value":201, + "check_result":"pass" + }, + { + "check":"headers.Content-Type", + "expect":"application/json", + "comparator":"eq", + "check_value":"application/json", + "check_result":"pass" + }, + { + "check":"content.success", + "expect":true, + "comparator":"eq", + "check_value":true, + "check_result":"pass" + }, + { + "check":"content.msg", + "expect":"user created successfully.", + "comparator":"eq", + "check_value":"user created successfully.", + "check_result":"pass" + } + ] + }, + "meta_datas_expanded":[ + { + "name":"/api/users/1548560768589", + "data":[ + { + "request":{ + "url":"http://127.0.0.1:5000/api/users/1548560768589", + "method":"POST", + "headers":{ + "User-Agent":"python-requests/2.18.4", + "Accept-Encoding":"gzip, deflate", + "Accept":"*/*", + "Connection":"keep-alive", + "device_sn":"mmHBimjzK4Pk6mg", + "token":"C3olNHYCMWfSQkCW", + "Content-Type":"application/json", + "Content-Length":"39" + }, + "body":"{"name": "user1", "password": "123456"}" + }, + "response":{ + "ok":true, + "url":"http://127.0.0.1:5000/api/users/1548560768589", + "status_code":201, + "reason":"CREATED", + "cookies":{}, + "encoding":"None", + "headers":{ + "Content-Type":"application/json", + "Content-Length":"54", + "Server":"Werkzeug/0.14.1 Python/3.6.5+", + "Date":"Sun, 27 Jan 2019 03:46:08 GMT" + }, + "content_type":"application/json", + "json":{ + "success":true, + "msg":"user created successfully." + } + } + } + ], + "stat":{ + "response_time_ms":2.8, + "elapsed_ms":1.748, + "content_size":54 + }, + "validators":[ + { + "check":"status_code", + "expect":201, + "comparator":"eq", + "check_value":201, + "check_result":"pass" + }, + { + "check":"headers.Content-Type", + "expect":"application/json", + "comparator":"eq", + "check_value":"application/json", + "check_result":"pass" + }, + { + "check":"content.success", + "expect":true, + "comparator":"eq", + "check_value":true, + "check_result":"pass" + }, + { + "check":"content.msg", + "expect":"user created successfully.", + "comparator":"eq", + "check_value":"user created successfully.", + "check_result":"pass" + } + ] + } + ], + "response_time":"2.80" + } + ], + "name":"testcase description", + "in_out":{ + "in":{}, + "out":{} + } + } + ] +} \ No newline at end of file diff --git a/data/reports/1548561170.html b/data/reports/1548561170.html new file mode 100644 index 00000000..ca7c393b --- /dev/null +++ b/data/reports/1548561170.html @@ -0,0 +1,774 @@ + + + + - TestReport + + + + +

Test Report:

+ +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + +
START AT2019-01-27 11:52:50
DURATION0.039 seconds
PLATFORMHttpRunner 2.0.2 CPython 3.7.0 Darwin-18.2.0-x86_64-i386-64bit
STATTESTCASES (success/fail)TESTSTEPS (success/fail/error/skip)
total (details) =>1 (1/0)2 (2/0/0/0)
+ +

Details

+ + + +

testcase description

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TOTAL: 2SUCCESS: 2FAILED: 0ERROR: 0SKIPPED: 0
StatusNameResponse TimeDetail
success/api/get-token10.05 ms + + + + log-1 + + + + + +
success/api/users/15485611704972.88 ms + + + + log-1 + + + + + +
+ + \ No newline at end of file diff --git a/data/reports/1548561464.html b/data/reports/1548561464.html new file mode 100644 index 00000000..74b0cc68 --- /dev/null +++ b/data/reports/1548561464.html @@ -0,0 +1,804 @@ + + + + - TestReport + + + + +

Test Report:

+ +

Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + +
START AT2019-01-27 11:57:44
DURATION0.035 seconds
PLATFORMHttpRunner 2.0.2 CPython 3.7.0 Darwin-18.2.0-x86_64-i386-64bit
STATTESTCASES (success/fail)TESTSTEPS (success/fail/error/skip)
total (details) =>1 (0/1)2 (1/1/0/0)
+ +

Details

+ + + +

testcase description

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TOTAL: 2SUCCESS: 1FAILED: 1ERROR: 0SKIPPED: 0
StatusNameResponse TimeDetail
success/api/get-token9.01 ms + + + + log-1 + + + + + +
failure/api/users/10002.62 ms + + + + log-1 + + + + + traceback + + + +
+ + \ No newline at end of file diff --git a/data/testerhome-login.har b/data/testerhome-login.har new file mode 100644 index 00000000..f54e6707 --- /dev/null +++ b/data/testerhome-login.har @@ -0,0 +1 @@ +{"log":{"version":"1.2","creator":{"name":"Charles Proxy","version":"4.2.6"},"entries":[{"startedDateTime":"2019-04-19T14:14:14.014+08:00","time":227,"request":{"method":"GET","url":"https://testerhome.com/account/sign_in","httpVersion":"HTTP/1.1","cookies":[{"name":"_ga","value":"GA1.2.162109905.1516957848"},{"name":"gsScrollPos-3842","value":"0"},{"name":"gsScrollPos-3871","value":""},{"name":"gsScrollPos-1094","value":"0"},{"name":"gsScrollPos-1837","value":"0"},{"name":"gsScrollPos-1993","value":""},{"name":"gsScrollPos-324","value":"0"},{"name":"gsScrollPos-861","value":"0"},{"name":"gsScrollPos-1568","value":"0"},{"name":"gsScrollPos-2909","value":"0"},{"name":"gsScrollPos-2429","value":""},{"name":"gsScrollPos-5380","value":"0"},{"name":"gsScrollPos-5417","value":"0"},{"name":"gsScrollPos-1937750581","value":""},{"name":"gsScrollPos-1937751827","value":"0"},{"name":"gsScrollPos-1937751846","value":"0"},{"name":"gsScrollPos-1937756244","value":"0"},{"name":"gsScrollPos-1937759407","value":"0"},{"name":"gsScrollPos-968","value":"0"},{"name":"gsScrollPos-3085","value":""},{"name":"hasSkipWelcomePage","value":"1"},{"name":"gsScrollPos-1874","value":"0"},{"name":"gsScrollPos-2049","value":"0"},{"name":"gsScrollPos-3406","value":""},{"name":"user_id","value":"NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f"},{"name":"gsScrollPos-891","value":"0"},{"name":"gsScrollPos-931","value":""},{"name":"gsScrollPos-2436","value":"0"},{"name":"_gid","value":"GA1.2.113994526.1555584877"},{"name":"_homeland_session","value":"pMGgyE29RXzrANx5RkqLjCMIE%2FHB%2FxbtHPs1pETwS54JTS%2BaV7QSR10JxAXa6wXxVIKDfMclOhVyQF0ztWr51Z3pEDEZ8P5HRgW7UnnUXHU9LqyAA%2FLF8V3LptG6jYLODkS43KqwfgHQfq1oF9X%2FLDyuYhyfJH%2FQFJLanFfH7lq%2Bg6wJXSVBTvDDZh8m5wITIVhYd63fo8B5Eu3XkSbPpY%2B3PivBAyRiC5AjXEWnhDTDIGA%2BYXZ5hjGIOJRdhjhD1nF5OPq%2FNfG96u6yp5EBWbDknUicZ2YHGQ%3D%3D--G4cowQKL8hiscX9U--48zlnNPSvWTk2tgnQlIhzg%3D%3D"},{"name":"_gat","value":"1"}],"headers":[{"name":"Host","value":"testerhome.com"},{"name":"Connection","value":"keep-alive"},{"name":"Cache-Control","value":"max-age=0"},{"name":"Upgrade-Insecure-Requests","value":"1"},{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"},{"name":"Accept","value":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"},{"name":"Referer","value":"https://testerhome.com/notifications/personal"},{"name":"Accept-Encoding","value":"gzip, deflate, br"},{"name":"Accept-Language","value":"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"},{"name":"Cookie","value":"_ga=GA1.2.162109905.1516957848; gsScrollPos-3842=0; gsScrollPos-3871=; gsScrollPos-1094=0; gsScrollPos-1837=0; gsScrollPos-1993=; gsScrollPos-324=0; gsScrollPos-861=0; gsScrollPos-1568=0; gsScrollPos-2909=0; gsScrollPos-2429=; gsScrollPos-5380=0; gsScrollPos-5417=0; gsScrollPos-1937750581=; gsScrollPos-1937751827=0; gsScrollPos-1937751846=0; gsScrollPos-1937756244=0; gsScrollPos-1937759407=0; gsScrollPos-968=0; gsScrollPos-3085=; hasSkipWelcomePage=1; gsScrollPos-1874=0; gsScrollPos-2049=0; gsScrollPos-3406=; user_id=NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f; gsScrollPos-891=0; gsScrollPos-931=; gsScrollPos-2436=0; _gid=GA1.2.113994526.1555584877; _homeland_session=pMGgyE29RXzrANx5RkqLjCMIE%2FHB%2FxbtHPs1pETwS54JTS%2BaV7QSR10JxAXa6wXxVIKDfMclOhVyQF0ztWr51Z3pEDEZ8P5HRgW7UnnUXHU9LqyAA%2FLF8V3LptG6jYLODkS43KqwfgHQfq1oF9X%2FLDyuYhyfJH%2FQFJLanFfH7lq%2Bg6wJXSVBTvDDZh8m5wITIVhYd63fo8B5Eu3XkSbPpY%2B3PivBAyRiC5AjXEWnhDTDIGA%2BYXZ5hjGIOJRdhjhD1nF5OPq%2FNfG96u6yp5EBWbDknUicZ2YHGQ%3D%3D--G4cowQKL8hiscX9U--48zlnNPSvWTk2tgnQlIhzg%3D%3D; _gat=1"},{"name":"If-None-Match","value":"W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\""}],"queryString":[],"headersSize":1666,"bodySize":0},"response":{"_charlesStatus":"COMPLETE","status":200,"statusText":"OK","httpVersion":"HTTP/1.1","cookies":[{"name":"_homeland_session","value":"%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D","path":"/","domain":null,"expires":"Thu, 18 Jul 2019 06:14:16 -0000","httpOnly":true,"secure":true,"comment":null,"_maxAge":null}],"headers":[{"name":"Server","value":"nginx/1.10.2"},{"name":"Date","value":"Fri, 19 Apr 2019 06:14:16 GMT"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Transfer-Encoding","value":"chunked"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Frame-Options","value":"SAMEORIGIN"},{"name":"X-XSS-Protection","value":"1; mode=block"},{"name":"X-Content-Type-Options","value":"nosniff"},{"name":"X-Download-Options","value":"noopen"},{"name":"X-Permitted-Cross-Domain-Policies","value":"none"},{"name":"Referrer-Policy","value":"strict-origin-when-cross-origin"},{"name":"ETag","value":"W/\"587c0b9f9cf24943552b421690023012\""},{"name":"Cache-Control","value":"max-age=0, private, must-revalidate"},{"name":"Content-Security-Policy","value":";"},{"name":"Set-Cookie","value":"_homeland_session=%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D; path=/; expires=Thu, 18 Jul 2019 06:14:16 -0000; secure; HttpOnly"},{"name":"X-Request-Id","value":"d12cd1e4-2289-428f-8755-cabf6ad974ab"},{"name":"X-Runtime","value":"0.041729"},{"name":"Strict-Transport-Security","value":"max-age=15552000; includeSubDomains"},{"name":"Content-Encoding","value":"gzip"},{"name":"Connection","value":"keep-alive"}],"content":{"size":12610,"compression":8072,"mimeType":"text/html; charset=utf-8","text":"\n\n\n\n \n \n \n \n Sign In · TesterHome<\/title>\n <link rel=\"icon\" href=\"/assets/favicon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"apple-touch-icon-precomposed\" href=\"/assets/ios-icon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"shortcut icon\" href=\"/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"apple-touch-icon\" href=\"/assets/favicon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\">\n\n <!-- http://www.favicon-generator.org/ -->\n <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-icon-57x57.png\">\n <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"/apple-icon-60x60.png\">\n <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-icon-72x72.png\">\n <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/apple-icon-76x76.png\">\n <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-icon-114x114.png\">\n <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"/apple-icon-120x120.png\">\n <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-icon-144x144.png\">\n <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"/apple-icon-152x152.png\">\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-icon-180x180.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/android-icon-192x192.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/favicon-96x96.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n <link rel=\"manifest\" href=\"/manifest.json\">\n <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n <meta name=\"msapplication-TileImage\" content=\"/ms-icon-144x144.png\">\n <meta name=\"theme-color\" content=\"#ffffff\">\n\n <meta name=\"apple-mobile-web-app-capable\" content=\"no\">\n <meta content='True' name='HandheldFriendly'/>\n <link rel=\"alternate\" type=\"application/rss+xml\" title=\"订阅最新帖\" href=\"https://testerhome.com/topics/feed\"/>\n <link rel=\"stylesheet\" media=\"screen\" href=\"/assets/front-15185e3983cd68677b04329670c6a6b7fedecb51f1b39ff7fac48180c1850eaa.css\" data-turbolinks-track=\"reload\" />\n \n \n <meta name=\"action-cable-url\" content=\"/cable\" />\n <meta name=\"csrf-param\" content=\"authenticity_token\" />\n<meta name=\"csrf-token\" content=\"0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA==\" />\n <meta itemprop=\"name\" content=\"TesterHome\"/>\r\n <meta itemprop=\"image\" content=\"//10.url.cn/qqcourse_logo_ng/ajNVdqHZLLAH8YXbXMDFib2SnIqhac60vw3BspyLd0TO82PiaJ2xfbvXynrocic7ajbCGribAic88wgc/\"/>\r\n\r\n<style type=\"text/css\">\r\n\r\n.blog-description{\r\n text-align: left;\r\n padding: 18px;\r\n}\r\n.blog-description:first-letter {\r\nfont-size : 200%;\r\nfont-weight : bold;\r\nfloat : left;\r\nmargin-right: 3px;\r\n}\r\n\r\n/*\r\n.new-topic:before {\r\n content:url(\"https://testerhome.com/uploads/photo/2019/d80c7d7c-7727-45b2-a98d-2c87a68c65e8.png\");\r\n position: absolute;\r\n left:60%;\r\n margin-top:-35px;\r\n}\r\n*/\r\n<\/style>\n\n <script src=\"/assets/app-04f83676fc1f6160a4410556b3298892a4536d9e8ee6c9b54df9191dd8dd9060.js\" data-turbolinks-track=\"reload\"><\/script>\n \n<\/head>\n<body class=\"page-sessions\" data-controller-name=\"sessions\">\n\n <div id=\"welcome-page\" class=\"hide-page\">\n <div \r\nstyle=\"background-image: url(https://testerhome.com/uploads/photo/2019/971bc4ea-b6eb-4d64-b6a1-1e71928314af.jpg); background-position: top center; background-attachment: fixed; background-size: cover; background-repeat: no-repeat; height:100%\">\r\n<\/div>\n <div class=\"welcome-action-button\">\n <div class=\"btn-group\">\n <a id=\"welcome-page-skip\" type=\"button\" class=\"btn btn-primary\">跳过<\/a>\n <\/div>\n <div class=\"btn-group\">\n <a href=\"http://2019.test-china.org/\" type=\"button\" class=\"btn btn-info\">查看详情<\/a>\n <\/div>\n <\/div>\n <\/div>\n\n <div id=\"main-page\">\n<div class=\"header\">\n <nav class=\"navbar navbar-inverse navbar-fixed-top navbar-default\">\n <div class=\"container\">\n <div class=\"navbar-header\" id=\"navbar-header\" data-turbolinks-permanent>\n <button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#main-navbar-collapse\">\n <span class=\"sr-only\">Toggle<\/span>\n <i class=\"fa fa-reorder\"><\/i>\n <\/button>\n <a href=\"/\" class=\"navbar-brand\"><b>TesterHome<\/b><\/a>\n\n <\/div>\n <div class=\"collapse navbar-collapse\" id=\"main-navbar-collapse\">\n \n <div id=\"main-nav-menu\">\n <ul class=\"nav navbar-nav\">\n <li class=\"\"><a href=\"/topics\">Topics<\/a><\/li><li class=\"\"><a href=\"/bugs\">Bug Tracker<\/a><\/li><li class=\"\"><a href=\"/questions\">QA<\/a><\/li><li class=\"\"><a href=\"/teams\">Teams<\/a><\/li><li class=\"\"><a href=\"/jobs\">招聘<\/a><\/li><li class=\"\"><a href=\"/wiki\">Wiki<\/a><\/li><li class=\"\"><a href=\"/opensource_projects\">开源项目<span class=\"badge-new\">新<\/span><\/a><\/li>\n \n <\/ul>\n<\/div>\n <\/div>\n <ul class=\"nav user-bar navbar-nav navbar-right\">\n <li><a href=\"/account/sign_up\">Sign Up<\/a><\/li>\n <li><a href=\"/account/sign_in\">Sign In<\/a><\/li>\n<\/ul>\n\n<ul class=\"nav navbar-nav user-bar navbar-right\">\n <li class=\"nav-search hidden-xs hidden-sm hidden-md\">\n <form class=\"navbar-form form-search active\" action=\"/search\" method=\"GET\">\n <div class=\"form-group\">\n <input class=\"form-control\" name=\"q\" type=\"text\" value=\"\" placeholder=\"搜索本站内容\" />\n <\/div>\n <i class=\"fa btn-search fa-search\"><\/i>\n <\/form>\n <\/li>\n\n<\/ul>\n\n <\/div>\n <\/nav>\n <div id=\"corner\" class=\"\">\n <a id=\"cornertip\" href=\"\">\n <div id=\"c-content\">\n <div id=\"c-button\">欢迎<\/div>\n <\/div>\n <\/a>\n <\/div>\n<\/div>\n\n\n\n<div id=\"main\" class=\"main-container container\">\n \n \n \n<div class=\"row\">\n <div class=\"col-md-5 col-md-offset-2\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading\">Sign In<\/div>\n <div class=\"panel-body\">\n <form class=\"simple_form \" id=\"new_user\" novalidate=\"novalidate\" action=\"/account/sign_in\" accept-charset=\"UTF-8\" data-remote=\"true\" method=\"post\"><input name=\"utf8\" type=\"hidden\" value=\"✓\" />\n <div class=\"form-group\">\n <input type=\"email\" class=\"form-control input-lg\" placeholder=\"用户名 / Email\" name=\"user[login]\" id=\"user_login\" />\n <\/div>\n <div class=\"form-group\">\n <input type=\"password\" class=\"form-control input-lg\" placeholder=\"密码\" name=\"user[password]\" id=\"user_password\" />\n <\/div>\n\n <div class=\"from-group checkbox\">\n <label for=\"user_remember_me\">\n <input name=\"user[remember_me]\" type=\"hidden\" value=\"0\" /><input type=\"checkbox\" value=\"1\" name=\"user[remember_me]\" id=\"user_remember_me\" /> Remember me (2 months)\n <\/label>\n <\/div>\n <div class=\"form-actions\">\n <input type=\"submit\" name=\"commit\" value=\"Sign In\" class=\"btn btn-primary btn-lg btn-block\" data-disable-with=\"Sign In...\" />\n <\/div>\n<\/form> <\/div>\n <\/div>\n <\/div>\n <div class=\"col-md-3\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading\">Sign in with other services<\/div>\n <ul class=\"list-group\">\n <li class=\"list-group-item\"><a class=\"btn btn-default btn-lg btn-block\" href=\"/account/auth/github\"><i class='fa fa-github'><\/i> GitHub<\/a> <\/li>\n <\/ul>\n <\/div>\n\n <div class=\"panel panel-default\">\n <ul class=\"list-group\">\n\n <li class=\"list-group-item\"><a href=\"/account/sign_up\">Sign Up<\/a><\/li>\n\n <li class=\"list-group-item\"><a href=\"/account/password/new\">Forgot your password?<\/a><\/li>\n\n <li class=\"list-group-item\"><a href=\"/account/confirmation/new\">Havn't received your confirmation email?<\/a><\/li>\n\n <li class=\"list-group-item\"><a href=\"/account/unlock/new\">Havn't received your unlock email?<\/a><\/li>\n<\/ul>\n <\/div>\n <\/div>\n<\/div>\n\n<script>\n $('#new_user').on('ajax:error', function(event, xhr, status, error) {\n App.alert(xhr.responseText, '#main');\n })\n<\/script>\n\n<\/div>\n\n <footer class=\"footer\" id=\"footer\" data-turbolinks-permanent>\n <div class=\"container\">\n \r\n\r\n<div class=\"row\">\r\n <div class=\"col-sm-9\">\r\n <div class=\"media\">\r\n <div class=\"media-left\">\r\n <img src=\"https://testerhome.com/uploads/photo/2016/274e7ebc8ad0db0b7e718fceea3628a9.png!large\" style=\"width:48px;\">\r\n <\/div>\r\n <div class=\"media-body\">\r\n\r\n <div class=\"links\">\r\n <a href=\"https://testerhome.com/wiki/about\">关于<\/a> / \r\n <a href=\"https://testerhome.com/users\">活跃用户<\/a> / \r\n <a href=\"http://test-china.org/\" target=\"_blank\">中国移动互联网测试技术大会<\/a> / \r\n <a href=\"/topics/node13\">反馈<\/a> / \r\n <a href=\"https://github.com/testerhome\">Github<\/a> / \r\n <a href=\"https://testerhome.com//api-doc/\">API<\/a> / \r\n <a href=\"/wiki/spreadtesterhome\">帮助推广<\/a>\r\n\r\n <\/div>\r\n <div class=\"copyright\" style=\"font-size:14px; color:#9CA4A9;margin-top:0px;margin-bottom:5px\">\r\n TesterHome 移动测试社区,由众多移动测试工作者维护,致力于推进国内测试技术。Inspired by RubyChina\r\n <\/div>\r\n<div class=\"links\" data-no-turbolink=\"\">\r\n<span style=\"font-size:14px; color:#666;\">友情链接<\/span><span style=\"margin-left:5px\">\r\n <a style=\"color:#317DDA;\" href=\"http://wetest.qq.com/?from=links_testerhome\" target=\"_blank\">WeTest腾讯质量开放平台<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://www.infoq.com/cn\" target=\"_blank\">InfoQ<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://www.testtao.com/portal.php\" target=\"_blank\">测试之道<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"https://www.testwo.com/\" target=\"_blank\">测试窝<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://tieba.baidu.com/f?ie=utf-8&kw=%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95&fr=search\" target=\"_blank\">百度测试吧<\/a> /\r\n <a style=\"color:#317DDA;\" href=\"http://www.itdks.com/\" target=\"_blank\">IT大咖说<\/a>\r\n<\/span>\r\n <\/div>\r\n <div class=\"links\" style=\"margin-top:0px\" data-no-turbolink=\"\">\r\n <a href=\"?locale=zh-CN\" rel=\"nofollow\">简体中文<\/a> / <a href=\"?locale=zh-TW\" rel=\"nofollow\">正體中文<\/a> / <a href=\"?locale=en\"\r\n rel=\"nofollow\">English<\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n <div class=\"col-sm-3 friends\">\r\n <a href=\"http://www.ucloud.cn/?utm_source=zanzhu&utm_campaign=testerhome&utm_medium=display&utm_content=yejiao&ytag=testerhome_logo\"\r\n target=\"_blank\" rel=\"twipsy\" style=\"display:inline-block;margin-right:5px;\" data-original-title=\"本站服务器由 Ucloud 赞助\"><img src=\"https://testerhome.com/photo/2016/4ce97c93f9433f654884c4839408327a.png\" style=\"height:28px\"><\/a>\r\n\r\n <a href=\"http://www.sendcloud.net/\"\r\n target=\"_blank\" rel=\"twipsy\" style=\"display:inline-block;margin-right:5px;\" data-original-title=\"邮件服务由 SendCloud 赞助\"><img src=\"https://testerhome.com/uploads/photo/2017/0f9fb5db-2472-4430-9f7e-6c051f28c3c9.png\" style=\"height:28px\"><\/a>\r\n\r\n <\/div>\r\n<\/div>\r\n\r\n\r\n\n <\/div>\n <\/footer>\n\n<script type=\"text/javascript\" data-turbolinks-eval=\"false\">\n App.root_url = \"https://testerhome.com/\";\n App.asset_url = \"\";\n App.twemoji_url = \"https://twemoji.b0.upaiyun.com/2\";\n App.locale = \"en\";\n<\/script>\n\n<script>\n ga('create', 'UA-45014075-1', 'auto');\n ga('require', 'displayfeatures');\n ga('send', 'pageview');\n<\/script>\n<div class=\"zoom-overlay\"><\/div>\n<\/div>\n<\/body>\n<\/html>\n"},"redirectURL":null,"headersSize":0,"bodySize":4538},"serverIPAddress":"106.75.214.88","cache":{},"timings":{"dns":12,"connect":125,"ssl":91,"send":1,"wait":82,"receive":7}},{"startedDateTime":"2019-04-19T14:14:14.971+08:00","time":83,"request":{"method":"GET","url":"https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png","httpVersion":"HTTP/1.1","cookies":[{"name":"_ga","value":"GA1.2.162109905.1516957848"},{"name":"gsScrollPos-3842","value":"0"},{"name":"gsScrollPos-3871","value":""},{"name":"gsScrollPos-1094","value":"0"},{"name":"gsScrollPos-1837","value":"0"},{"name":"gsScrollPos-1993","value":""},{"name":"gsScrollPos-324","value":"0"},{"name":"gsScrollPos-861","value":"0"},{"name":"gsScrollPos-1568","value":"0"},{"name":"gsScrollPos-2909","value":"0"},{"name":"gsScrollPos-2429","value":""},{"name":"gsScrollPos-5380","value":"0"},{"name":"gsScrollPos-5417","value":"0"},{"name":"gsScrollPos-1937750581","value":""},{"name":"gsScrollPos-1937751827","value":"0"},{"name":"gsScrollPos-1937751846","value":"0"},{"name":"gsScrollPos-1937756244","value":"0"},{"name":"gsScrollPos-1937759407","value":"0"},{"name":"gsScrollPos-968","value":"0"},{"name":"gsScrollPos-3085","value":""},{"name":"hasSkipWelcomePage","value":"1"},{"name":"gsScrollPos-1874","value":"0"},{"name":"gsScrollPos-2049","value":"0"},{"name":"gsScrollPos-3406","value":""},{"name":"user_id","value":"NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f"},{"name":"gsScrollPos-891","value":"0"},{"name":"gsScrollPos-931","value":""},{"name":"gsScrollPos-2436","value":"0"},{"name":"_gid","value":"GA1.2.113994526.1555584877"},{"name":"_gat","value":"1"},{"name":"_homeland_session","value":"%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D"}],"headers":[{"name":"Host","value":"testerhome.com"},{"name":"Connection","value":"keep-alive"},{"name":"Pragma","value":"no-cache"},{"name":"Cache-Control","value":"no-cache"},{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"},{"name":"Accept","value":"image/webp,image/apng,image/*,*/*;q=0.8"},{"name":"Referer","value":"https://testerhome.com/account/sign_in"},{"name":"Accept-Encoding","value":"gzip, deflate, br"},{"name":"Accept-Language","value":"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"},{"name":"Cookie","value":"_ga=GA1.2.162109905.1516957848; gsScrollPos-3842=0; gsScrollPos-3871=; gsScrollPos-1094=0; gsScrollPos-1837=0; gsScrollPos-1993=; gsScrollPos-324=0; gsScrollPos-861=0; gsScrollPos-1568=0; gsScrollPos-2909=0; gsScrollPos-2429=; gsScrollPos-5380=0; gsScrollPos-5417=0; gsScrollPos-1937750581=; gsScrollPos-1937751827=0; gsScrollPos-1937751846=0; gsScrollPos-1937756244=0; gsScrollPos-1937759407=0; gsScrollPos-968=0; gsScrollPos-3085=; hasSkipWelcomePage=1; gsScrollPos-1874=0; gsScrollPos-2049=0; gsScrollPos-3406=; user_id=NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f; gsScrollPos-891=0; gsScrollPos-931=; gsScrollPos-2436=0; _gid=GA1.2.113994526.1555584877; _gat=1; _homeland_session=%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D"}],"queryString":[],"headersSize":1591,"bodySize":0},"response":{"_charlesStatus":"COMPLETE","status":200,"statusText":"OK","httpVersion":"HTTP/1.1","cookies":[],"headers":[{"name":"Server","value":"nginx/1.10.2"},{"name":"Date","value":"Fri, 19 Apr 2019 06:14:17 GMT"},{"name":"Content-Type","value":"image/png"},{"name":"Content-Length","value":"15229"},{"name":"Last-Modified","value":"Sat, 19 Nov 2016 15:26:39 GMT"},{"name":"ETag","value":"\"58306f2f-3b7d\""},{"name":"Expires","value":"Sat, 18 Apr 2020 06:14:17 GMT"},{"name":"Cache-Control","value":"max-age=31536000"},{"name":"Cache-Control","value":"public"},{"name":"Accept-Ranges","value":"bytes"},{"name":"Connection","value":"keep-alive"}],"content":{"size":15229,"mimeType":"image/png","text":"iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAACXBIWXMAAB7CAAAewgFu0HU+AAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAADCqSURBVHja7J13eFzV8b/fu7sq7rg33HEv2NgUB4jBYMABbNMhhFASiBNCTfiGEEhCDb8QcEKASECI6Rj3FveG7dgGy73bslwkF9mSvCtpV1vv749zxUpy0Vlpd6WV5n0ePbu27hbde+dzZubMmWOYpokgCPUTm5wCQRABEARBBEAQBBEAQRBEAARBEAEQBEEEQBCEuoaj7D+ajZcTUsexA4OAHwKXWs+7mt58k4BnH0mNvwNzJX738sLPOh6W01W3MU0To2whkAhAnfTwhgJXW0Z/BdDstBuh+Ahm0IOR0gJsSRj2ZDAc+zBs3wALgUWudCNfTqcIgFD7aQfcAFwHXAu0ruwFoeJsCLiVXpQKgC3Zep4CNkcIw54BzAcWAWtc6UZATrUIgFA73PrLLaO/ARgMGJG8QVgASjHUj82OYUsGSxAMSxSw2Z1gLALmAvNc6cZxuQwiAEL8aA5cD9xsGX2L6rzZ6QLA6WJgWN6BLUkJgj1FPTccIQxbBvBfSxAyXOlGSC6RCIAQXXoBY4AbrVjeEa03PrcAnEEQDBsYjvJiYE8BwwGGLReYA8wGFrrSDbdcOhEAIXJswGXAOOunZ6w+KDIBOIN3YHNYoUKSChXsKSpUMOweYAkwC5jjSjeOymUVARDOTgoqY3+LNdq3i8eHVl0AzhIq2EtFIDkcNtgcITDWAzOB2a50Y6tcbhEAAZoCoy2jH239O65ERwDO4MAYtjLeQbm8ARi2LMszmA2skFkFEYD6RFtgrOXaj7RG/hojNgJwlrxB6RSj3ZpVMOxgs58CY57lHcx3pRtOuUVquQA40+QERkKz8fSwRvlxwHDiXIZtt8HA8+HSHnBJd+jfEbq0VL/LycmmqNhNZi5sz4GMLNh4CE7FTBNsYLNb4YE1tWhPtrwDu08VIBmzgJmudOOQ3D0iAIlq9BcRTuINjPfn92oH1/aHEX3g8p7QJPXMx2VnZ+N2uyvcHJB5AtZlqp+MA1Dij5WDYD99VuH7UMHIwLDPAKa70o3tcleJANRmg7ejpuhKR/ou8fz81CS4ohdcPxBG9YduFWoAgyHYmq0M+tv9arQ/mAcUZ9O9hZv+HWFoVxjSBc5rWP613gBsPghrMmHNPtgbs9IfNatQPlywBAFjL4ZtOoZtBrBO6g1EAGqD0TuAq4DbgFuBNvH8/M4t1Sh/w0D4YW9okFz+93uOweLtsGIXrN4LhSWV5wAMA3q0tsKFHjCsqxKXshx3wardsGqvEpOYegeloUJpRaI9GQz7UTBmYdimActd6YZP7kYRgHgZfTJwDXA7KpnXMl6f7bDBZRfAdQNg1ADo16H870v8sGoPLNgKi7ZD1onK37OyJGCKAy7sAsO7w/Ce0LPt6d5BRpb63JV74UhBnEKFUjGwJZ0C5mHYp6NKk4vEvEUAom30qagFNncANwHnxeuzWzZWbv0NA+HqvtC0QfnfH8pTo/z8rfDNbvBEOBZGOgvQtqkKNa7opRKKFb2Dvcfgmz2wfBfsPKLyCTELFb5fsJRcWo1YgmEsAWZg2Ge40o2TYuoiAFU1+obAj6yR/kdAk3h9dqcWcNNg9TP8ApXFLyUQgrX7YOE2WLQNdhyp3mdVZxowxaHyBlf0hit7Qofm5X9/ohBW7lZisD5LeQsxEQPDHp5a/H7xUlIQw7YKjJkYtumudOOAmL0IQGVG3wS1yOZWVGFOw3h9dv+OcONguOlCuLBz+d/lFSm3fv5WWLYTXJ7ofW406wB6tlW5iBF9VHhilFmX6PGpBOI3Vu6goDgeoUJK2ZqDTRi2aZiBqa4PUnaIAIgAlP7dzVGlt7dabn5qPD7XZigX+sbBcPPg07P2h/Nhzib1s2afyuLHglgVArVuDFf2UWJwcTflLZQSDMGWw0oMVuyyZiNiowbhUKGsd2DYd2HYp4I5zfVB8gYRgHomAM3G0wo1VXcrKqGXHI/PTXaoEfLmwTD6QhVPl2V7DszdBHM2w+Y4lcDEvhIQUpNheA8Y0VvlDpo3Kv/7gyeVEKzYrYQhFKsd60pnFQyHVXOQimFzZIExDcM+FZtjrSvdMEUA6qAAWEZ/KyqRdzWqmUbMaZyq5uVvHqKy92ULckKmmkabuwlmb9LL2ieiAFT0fAZ1UmIwog90aXV6uLNil8obfLcffMF4eAdJVr1BSo6qMzCnYk/9xpVuBEUAElgAasroWzYOJ/FG9Cnv/voCyvWdvQnmbVZz6jVJvAWgIl1aqnP0w94q92Erkzco9qr6hWU71TSj2xdDMfi+6cn3i5dOGIZjJoZ9CjbHUle64RcBSAABqCmjb9tUxfNjh6isuKNM5r6oRM3Lz96osvdnKsiprwJQUThH9IGr+sDF3SG5zJXzBZW3tHyn8hDyi2MsBqXViGp68ZRhS5qNzT4VW/ICV7pRIgIgRk+H5iqeHzPk9Om6vKJwEm/FrlhNgdUtAShLo2S4vJeqfbiiJzRMKR86bT6kwoRlOyGnIIZi8H1/xBQlBjZHEYZ9vmHYp+BoMMeVbhSLANQjo+/cUhn8mCEqi192quu4S8XzMzeqUtlAAlSr11YBKEuyXXkEV/dVHkKLCknEvceUECzbpcqfYy0GGA4MRyrYkj0YtsUYtqmGo9FMV7pxSgSgDhp999Yw5iIYd5FaLFOWIwUqnp+1MbbTdfVZAMpSmkS8uq/66dj89OuxdKcKFTbHckahtOmJYVdViI5UHxjLgekES6YVftIuVwQggY2+dztl9LcMVUU6ZTmUpwx+1kYVl5oJPHGUaAJQkZ5tLTHoA73al//dySJYsVPlXzIOxFIMrGpEw45hc4A9OYhhX40Zmk7QN7U27rRUqwWg2XhaWkZ/ZzyNvn9HNcqPHaoEoCz7T8CsDTBjA2w8SJ0h0QWgYk5mZF+VRKw4o3DKbYUJO5Vo+2M6wVemJZo9JYRhXw/GNMzglMJP2maKAJzZ6Juj1tLfiSrOccTjRAw4X43ytwyFHhUW8+4+pox+eoYq0qmL1CUBKEvLxkoIRg2Ai7qUT9AWemDlHli6Q4VtJTFN0BrhAiRHapmS5NDUwoktd9RrAWg2nmaoJbV3obayiktFXulIf8uw05e3bs9Ro/zMDCUAdZ26KgBlOa9hOGdwSXdIKuNPenyq1mDJjljXGpT1DCwxsCXtAGMGId+Uwo/bbALMOi8A1oKbMZbRX0ecmmL27RAe6XtVcO+3ZatRfnoGZOZSr6gPAlCWJqlwZS8Y2U/1N0gt42d6A7A2E5btUFO3rpjO9p/W/Wg/hm0qocDkwo9brY+1GMRVAJqNpxFqld2dqO2sGsTjYvdpD+Mso+/T/swj/fT1sWxxJQJQm0lNVjUG1/Q7vdYgEFRLmJdYMwr5sZ7tNxylOQOwJR1QYhD8uvDjVt/FQgxiLgDNxtMAtZXVXaj19HFZWturXdjoK3bM2XkkPNLvOYZQzwWgLCkOuKxHuNagbOOVYAg2HVI5g6U7IdcVLzFIBVvSQQz7NMzA14UTW66LlhjERACszjmjrZH+JqBxPC7eBW3D7n3FKbtdR2GGZfS7ZHMqEQANHDYY1k15Blf1LV94ZJqwLUeJwZIdsaxCLCsGVucje/IhtYyZyUZSk2qtXIyaADQbTxIwyhrpxxGn3W16tFEj/a1DVSa/LHuOhY2+uh1zRADqNzZDFX+N7KemGNtUuLt3H1VhwtLtkBXrBmTfL1ZKBXvKIcOwTzPNwGTf5jfXejNeDMVNAFDz8iMto7+Fam5RrUu31uGRflCn8r/bdzzs3tfVKTsRgJrFMGBARyUE1/Q/vQox6yQs2a68g5jPIH0vBilgS8rGsE8D8+uS5T9b698/ORgLAbCh+t7fhWqD3TYeJ71LK2Xwtw49vVVWZq4a6adlqEy+IAIQT3q3U57BNf2hW4W+BjkFlhjsVCGDGcsqxLINUg3HYQxjKqHgV2USiGZVBcAALgHuRpXidozHie3cMjzSV6y9zzoRHum3HJabUASgdtCtFYzsD9f0hd4VZpxyXSpfsHh7jDseQfmeiDb7ATCmmEHvpKLPO28EQqViUJkADLGM/k6gazxOYKcWyuDHDVWVW2VX2R08qQx+Wkb8WmWJAAhVpWNzlUAc2U+FDGXv5ROFlhhsi/VipVLvwAoVYC8wHZvj88JP2m45kwD0t9z7u4Be8TpRpSP90K7lT9ShvPBIX5dq70UA6hdtmiohuLbf6esTThaqEGHxdnWPh2JdB2hLwkhp6S/8T7NU0zRDFQUgLmWIHc6DsRfBrcNUt9iyRn84Xxn8jAzYcDCxV9mJAAgVad1Y5Quu7X8GMShSFYiLYiwGRvJ5zsJP2rYxTdMXNwFo3SRs9MMvKP+H5xSER/qMA2L0IgD1g1aNLc+gv8pzlbWJvCJVfRiLZcyGo3FO4Wcde5im6Y2pADRvpNpl3TpMNXwsuxrryCmYuQGmrYfvssToRQDqNy0bq6nFUQNgcOfytpJfrGoMFkbLM3A03FP0WafBpml6KgqAD0iqzns3SYUbL1Sr7K7pV37V1YnCsNGv2ReHeEcQAUhQMTjbMuaThcorWLwNtmRX0YZsKZuKvuh6uWma7ooCcApoFun7NUiG0YPUSD+qf/nNIguKVbusaetVK+yg7OguAiBo06KRChOuG3B6mJDrUmKwcJsqfNP2om1Ja4q+6D7KNM3iKgtAikN9qVuGweiB5VdRFZbA3M1qld2SHbHuvCKIANSfnMG1A9QgO6jT6Xm0MX/XzgIsLvqq1zjTNIur3HFn3xvlV0t5fDBvixrpF21X+9QLghA9ThbBV2vVT5umSgiuG6DWwTSLZHG9GSxEFfid1nJLuxdKqfHP3qRG+nlbwe2ViyQI8SDXBZ+vUTm1Fc9F6vIFCkufVhSAiDed/kmaXAxBqCkaV2EfazPkd55NAIrklFafy9rDA1cmzvfNO1pIKOjFbnfU6u9pmiamabL5AEzaZgNbcr2/1xpZubeiSLxvFQKcUQAijtwdtsTY/Sae9GoD94xMnO+bmZlMMGjHKFuSWYsxjBImbZXMclkBKI5EAEI+F9aCoIoCUKj7HoGQMv6GKeDyyIVIZEpngswEqMYyDAObzU4cm+fWahomRy4AZtD7vZ3bKvxOuweqJPzqDoky8n//fW1Wa23hewGIyB79RafOJgBi1kKiuC1yDsqEAPp7GRiYPmf+2QQg4l6nqUlyEQShxgQgNfIQIFSYlXc2AdBOApYW+iQ75CIIQo0JgBUCRDILEDyyIg8riVJRALS3PvBZe6mliAAIQgKFAGZJIGeJ+2wCoF0Q7g1ICCAItcUD0A4BQsF8yvQFrLIHUBoCiAAIQs3RpEGEAmAG8ynTJbiiADgjFYAGUowlCDVG6SKgQs0JfNMMOs/lAWgLgMdXPgYRBCH+NLV223TqBu9mMC8qHkCpyyECIAg1KADWNKB2NW7Inx8VD6BUABpIDkAQajwEcOnW8IYCp4CgJQJVLwTy+MUDEISaxG6Ek4C6IYAZ9OZF1QNo2kAuhCDUBOdZW5YHQ/pJQALFJ8r+s6IA5Ot+eGnM0UQEQBBqhBaWAJyKoJ2j6Tl+jDJLKassAIWe8jGIIAjxpbklAPnF+q8JOvceP5cABHRFwOmREEAQaoMAFOgLQCiQOemcAqDtBZSGAE1T5UIIQk2GANoCEArmhQoP+jlHHYC+AFhJh2YN5UIIQk0KgG4IYIb8xykzA1AtASiddih1QwRBiC+tm0SYAwj5SgUgdC4BOKHzXqVuR3PxAAShRmjT1DJY3eqdoFfLAzipJQDiAQhCzYYAjdVjrmYrX1NTAI5G4gHYbeFYRBCE+NHKEoB83d08AsXHUWXA5xSAXC1vIhSOPcQLEOJJKFTuHq6XJNvDdndSUwBM76kjqKn+70/emRp6HdH9EgXFavQXAajf2O32OBl+yPI6HZTJY9XP+N/aw9sX1J8GDLlzjloeAOcSgOORCEBZV0RQrNwPj/0rfp83sDM8cmPN/K15ToOJS4LY7EmEgvHZEjozF7DV72Woba0EYK5T/zWB/VMPV1TOMwnAMd03LHU9SrORgiKrELI2x+/zflqDf6s3YPDFpiAY8dwP3oAE28wkVgJwXHcGIBQ8GTyx3l0xdjrbLEBA5z1zrQ9v20yMXhDiKgCWzR3X9ADMkC+bMhWA5xKAEJq1AKUfLh5AfUd26Yk37SL1AILeHCpMAZ5NAAByIvEA2okHIAhxpX1zK17XzQEEPaUCENIRgMM671n64aUliYIgxIcOlgAcPaXpo3md2VSoATiXABzSedMThZIDEIQa8QDOU49HCvSOD7mPZqG2/oueB1CaA2gnOQBBiBstG0OqIzIPIFSwLYszJPer5QEcs3IADVPgPFkUJAhxoaPl/p8sghKd+TrT9Pr3fHpaEVC1PQC3N9yP7PwWcmEEIR6c3zwy95+Q/7DpcwY4Q/lktTwAgOz88l9KEITY0rlledur1AEIluznDFOA5xKAY1bCoHIBKBAPQBDi6gFYtnYoT1MA/EX7KbMZiI4AhIBs8QAEofbRqUX5wbdSAfDm70MlALUFACAzEg+go3gAghAXurZSj4d1QwDX/j2cYQqwMgHYp/PmOfkSAghCvGjTBBpbnbgPaBXsE/Lvn5LJWdb3VN8DsASgswiAIMR+9G+jHnNdUOTVMv+jwdx1xZxhCrAyAdij84UOWQLQoTmkyk7BghBTureKaPTHDHr3cYY1ADoCsF/nA44UQIkfbEY4NhEEITZ0s2xs/0nNF/iLShOAEXsA+9DouxQy4YD1Zbq3lgskCDEVACsEyNL1ALynMgFfVQSgBM1lwfutL9O9jVwgQYhpDqBVZAIQch/ZYwmAGakAlHoBlQtArngAghBrmjVQC4Eg7HVXQtC/74udZxv9dQRAKxEoHoAgxJ4eln05PZCn0wo86DsQPLqy6FyhfGUCsCMSD6CHCIAgxIxe7dVjZq7e8WbAvYNzJAB1BGCbzgeVfqGOzaGBTAUKQkzo3U497j6qKQA+5w7OkQDUEYDtOh+UUwAev9om7IJ2cqEEIRb0sWxrj2bjfrM4Z7slAFUOAY6j0SE4GIJ91pfq30EulCBEmyR7OMe2S9MDCByev/1co7+OAGiHAdutDcX6igAIQtTp0QYcdvAHwzm3cxIKHPFnTsqLhgBohQE7RQAEIebxf+ZxCGhsi2gG3Dst9z9QXQHYGokA9OsoF0sQoi4A1sC6W3PnTtNftA3wRsMD0JoK3GHVDJ7fPLxcURCE6NDXmgLcrbl3t1mcvYFzVABG6gFUuvdTdgEUlag9G/tJGCAIUSPJDr0tAdiplwAM+fd9taGy0V9XAJxolASbJuyQPIAgRJ1e7SDFoRKAWjUAQV9m4NDc/GgJAMB3keQBLuwkF00QosUgy572HAOvxj4Apr9oE2oxnz9aArBe56DN1m4CQ7rIRROEaHFhZ/W45bDe8aavIMMyfjNaAqDlAWw8qB77dQSHTS6cIERFACwPYLPmbh3Bkxu/RbOtv66ZaiUUduSoOcrUJOgr04GCUG3aNFU/ZT3scw//Iadvw6u7qWT+P1IBcKMxHVjih53WdODQrnLxBKG6DDxfPea61E+l9h8s2WgG3J5oC4B2GJBxQARAEKLFsG7qcatm/I/PtU7X/Y9UANZHIgAXd5OLJwjVpXQgXX9A7/iQc9+KWAnAt1puQpZ67N0emjaQCygIVaVFo3CbvQwdATBNl3fDKxtiJQCbgUobEe0+Ci6PahM+/AK5iIJQndHfMCC/ONx2r5L4f32oMMuNRjfvqghAAFhTqQtiwhqrbvDynnIRBaHK8X/X8Ohvmhov8Bf+L5LRP1IBAFipc9Dqverxil5yEQWhygLQ3Yr/szTjf1fmcjSz/1UVgFVaB1m9hAd3hiayMlAQIqZNk/AeAFoCYJqnStb9fnOsBWAtao3xOdl0CApLVI/AyyQPIAgRc7nlPee69PYAMIOeVab7qDfSz4lUADw6eYBgCNbukzBAEKrK8B7lw+lKBaAkb1mk8X9VBABgcSRhwBWSCBSEiHDY4FJLANZo7c1FKJizZD6qAUjMBWCZlgBYyjW4i9QDCEIkXNhZddUKBGFdpo75+3d6N/7lCBFM/1VHAL5FNQk5dx7goKoHcNhgZD+5qIKgyw+svNnmw1CkEdWbvsKlaOTmoiUAAWBJpQeFYKm1fOj6AXJRBUGX0gTg//Tcf0LFOYviKQAA83QOWmDtKDBqgJoREATh3HRqAT3bquerdbbmNUMF3m+fW1MV9786AvBfNLqNLNqmZgRaN4FLusvFFYTKuLqvejycD3s1WoCbwZJFIeced1U/r6oCcASNHYNOFMK3+yUMEARdSvNly3boHW+W5M2uqvtfHQEAmBFJGHDDILm4gnAu2jSBAVYnraU7dazfLPJteWsxGt26akwA5m9Rj307QLfWcpEF4Vzuv2Go6r9tORovCPmXBw7991R1PrPKAuBMYwNwsLLjdh6BLGsp42jxAgThrIyywuRlu/RW/5m+U7OpQvFPtDwAbS9gnuUF3H6xXGRBOBPtm6nFcwALtXbjNDz+zElzqGL2P1oCMEXrIKub4EVdwiucBEEIc/0g5f4fdWp2/w35Fvu2vXOyup9bXQFYDVTarXzDQbWiyTDgNvECBOE0bhioHhds0XT/vQWTqcLin6gKgDMNE5hU6Zc1YarlBdwhAiAI5ejZNlz8M1/P/T/l2/r2XDRqcWLtAQB8qXPQZEsA+naQluGCUJYxQ9Tj3uN6xT8EvXP9+yc7o/HZ1RYAZxob0dg0ZOeRcGfTn/xALroggNr6+8bB6vmsjVqjP6bP+QXVmPuPtgcA8B+dgz77nxUGXAINU+TiC8LVfaFZA7X199xNWgJw0LP8gSXR+vxoCcAnOgmJyd+C26v6BEouQBDglovU47Kd4PTouP8ln4Wc+7y1SgCcaeQCcyo7rrAknAv4xdVy8YX6TacW4c6/0zdomWsw5No/MZrfIZqLdD/UOSjd6ifUv6P0CxTqN3dfpjbQOZwP6/drvCDkX+peMDartgrAfKDSFgbbc8L9AsePlJtAqJ80SYWxVvb/q7VqQ53KMAPF/yFKyb+oC4AzjRDwns6xaUvV440XQq92cjMI9TP2b5AMhR6YqZf9z/GufWZ6tL9HtPv0/AeN/QPnboY9x5T788R1cjMI9Qu7AXddFo79PTrLeYLeDwNHlpfUagFwpnEKjSnBkAn/WKie33kJnN9Cbgqh/jCyH7RrprplTVqnZaYlocIDH8Tiu8SiU9+baGxP9PW3kJ0PyQ54ZrTcFEL94V6rEG7pTjimU89nBqe55998JCEEwJnGQTTKg30BeMNqLfqTy6FHG7kxhLrPkM4w8Hz1/PP/6YT+dtP0nXqLKNT9x8sDAHhd5wt/thoyc9XeAc+PkZtDqPs8YtW/bDwIW7N1XmEsLp5+2cZYfZ+YCIAzjR3A15UdFwjBK7PU81uGwsXd5AYR6i6DO4W7Y7+/XNM8gyUTqGbTj5rwAABe0MkFTM+A77JUr4A37lYzA4JQFxl/jXrcdCjcLfvc1mlfX/R1/wWx/E4xEwBnGnuBjyo7zjThma/UzMCQLvDgD+VGEeoeF3cLe7ildTCVxP4QKHk1lqN/rD0AgJeASjct2HgQ/vONev78GGjZWG4YoW7x8FXq8bss9VO5ADjWF33db3asv1dMBcCZRg4qIVgpr8yCvCJo0QheulVuGKHuMPD8cBOcD3Rif8MOgaKXiHLZb014AABvAHsrOyi/GP44TT2/d3h4h1RBSHTuv0I9bs0ON8U5twDY1hZ9PeC/8fhuMRcAZxolwFM6x36+Ru2Iahjw1o9VtxRBSGQGnA9X9VHPP16pO/p74jL6x8sDwJnGXDT6BZgmPP2F6o7StwM8eq3cQEJi8/goNaBty4blu3UsMjkj1pn/uAtA6bkAKl3MsPMIvLtYPX/2RujcUm4iITEZPSgc+7+9SKPdt+EoDZlDdU4AnGlkAX/ROfb1uXAoTy2XfONuuZGExKNpKjx1vXo+b4te7G8kNdpra9xpcjy/py3O5+WvQGZlB3l8qjYA1IYJYy+SG0pILH49Sk1nuzwwQceht6UAxkuudCNUZwXASgg+qnPs/K0w0+qT9uY9UhsgJA4XdlKl7QD/XKymtysf/RtuNBq0+Tze3zXeHgDONBYAX+gc+5sv1clr3URCASExSE2CF8apkvbNh2BGhsaLHA1NMH7jSjfMOi8AFk8BeZUddKIwHArcNiy8g4og1FaevgG6tYISP7w8U6PXn2HHsKdOLvy49bKa+L41IgBWG/Hf6hw7dX14x5S/3wttm8pNJtROru6rBiqAN+dDlsbevYajkRN4sqa+s60Gz9fHwEKdA5/8HI67VB7g3Z+qeVVBqE20bQp/HKueL90B09brWF8S2Bz/V/hx66P1TgCsnYUfBlyVHZtXBI9+ouZRRw2Ah2TFoFCLsBnw8m3QtIEaqF6epfMqA8PRcJlv2zsf1uh3r8kPd6ZxCHha59hF2+Aja8Xgq7dBb2knLtQSHhulCn6CIXhhqpr6qxRHAyem+ZA348VQvRUAi490Q4E/TIXdx1SB0H8ehgZJcvMJNcv1A+A+q8nnO4s0F/vYkjAMxxOFn7Y/UNPfv8YFwAoFHgIKKjvW44MHPwCPX20t9todcgMKNUevdvDHcSontWAbfLpG0/W3JU8p/LT9J7UifKkNX8LqG/ArnWO358BzVrHkQz8MF1wIQjxp3kgVqKUmwZ6j8NIMjVp/AHtqVqjkxMPEqMtvQgqAJQJfAZ9pxQzfqF6CAO/cBz3byg0pxI8kO7x+B3Q4DwqK4emv1Ly/huvvwwzeUzxlyKna8rfYatm5fRTQ2v3015/C3uPQOBU+fkTlBQQhHjw/BoZ1U8vWn50MR09pmloo+LuiL7qtq01/S60SAGcaLuAeoFI9LSqB+99XeYH+HVWRkCDEmgeuhJsGq+evzIT1upt1G8aXRV/1fLu2/T21zQPAmcY6VEtxrXzAk9byibsvhV9dIzeoEDtGD4JfW/fYxJUwZ7O2mW0NHPrveOK4zj9hBcDir2hODX61Dt5bop6/fBtc0UtuVCH6XNwN/jROZfznbYF3lmi/9EQwb/OdJasfd9XGv6tWCoA1NfgTIEfn+Bemwqo9aouxjx+GTrLbsBBFeraFv92tkn/fZcGLMzQz/hj+UNHhez2Lbt9VW/+22uoB4EzjBHCXTj4gEIL7P4DD+dCqCUx6VJKCQnTo1AL+eZ9KNu89Dr/9UiX/NIzfNL35T7rnXLukNv99ttr85ZxprAZ+r3PsyUK4691wUvCjn4PdJjewUHXaNFHG37qJGlwe+wSKvHqvNX2u14unX/ZBbYz7E0YALN5CY6NRUEnBhz5UNdk/GgQv3iI3sVA1mjaAt+9THkCuCx77FE4Uab446J1cPG3YSzreqwiAXj7gQWCTzvH/3QJ/mq6ePzYKfjZCbmYhMlKT4J8/UbG/ywOPf6Y8AC1C/jXeLW89gkYHbBEAfRFwA2OBEzrH/3MR/HuFev7GXcobEAQt43fAhB+rDT1K/PDYZyr21zT+3f7MyXf6d088lSh/b8JEydbS4dsBn87xz0xS3oDdpvIBw7rJzS2cmyS76j15SXcoCcBTX6gNPfSMP3gscHTl7d6MF7MT6W9OqDSZM41vUBuMVB6GhVQ+YH2WmhGY9Cvo3lpucuHsxv+3u+EHPVWW/5kv4dv92safH8zbdEfJyl9uT7S/O+Hy5M400oF0nWM9PrjrPdh/Qk0Pzn4KOjSXm1043fhfu0MVkfmD8Nuv1B6VWpihwmDBtp96lvx4DbVkhV+dFgCLx4GlOgeeLISbJ8CRAji/Bcx4QomBIIAqHnv1NhjZVxn/779WRWV6xm/6Qqd2/dyz6M6FxGkzTxEA5QX4gFuBrTrHZ+fDuH8oMejdDiY/qqZ5BBn5/3InXNNfhYx/mALL9Gv2giHXvl+6F9wygwSY7qtrHgDONJzAjwCtpMvuY3DHu2pa56KuMOXX0DBFjKC+kmwl/Eb2hUAQnpsCS3Zov9wMFR54xj3vpi/RTEqLAMRGBLItEXDqHL/hANz+Dri9cGkP5QlIyXD1MM2EC3tJdahuPldaMf+zX8Pi7REYf3HOn91zr08HPIl+/RK+WNaZxlYrHNBS4nWZyhPw+FTSZ+pj4glUx4gTTQBSk2HCvSrb7wuqnacicPsx3Udfcc8e+SbgrgvXvk5UyzvTWIpqLKp1N67aA3daInB5T5j2mFrsUV+x2WzYbDbsdntEP+p19oT5O5ukwnv3hef5f/MFrNwTifEff6141lV/BYrryrU3yip4s/ERGV2to9l4nkH1EtDi8p7hXMC6TBUeuBLQqevWBK7sXvXXF5/cDkEvNnuSroZimiamaeLy2lmQlQzU7u2aWjRS/SN7t1fC/3Qk8/yA6Tnxt+KZV/y5Lhm/aZp1SwCsv+ElNDsKAQy/QIlA49RwjiCviHpFsGA7BNxgRDKam2pRvM2B4WhQqwWgTVP41/3QtRUUeuCJz2Hz4Qj+0pK8vxfP+MELQJ26M+qkAFh/xwQi2HDx0h5KBJo2gD3H1JRhTkH9EYBQcbYSgDrI+S2U8Xc4D/KL4defqBkhfePPf7t4xvA/1DXjLxWAurpi/mng37oHr8uEsf9QI3+vdrDwGfUoJDa92sG/f6aMP9cFD38UofF7ct8snjH8+bpo/N/nf+riH2UtIf4F8JXuazYcgBv+pkb+81vA/N+qegEhMRnaFd5/AFo1VoVgD/0bDpyMwPjdx/5f8cwrXwQK6/J5qrM9c5xpBIH7gWm6r9lzDK57Qz22bAxznoLrB4oxJRojeqtOPk0aqF17Hvq3bu9+ZfuhouxXimeNeK2uG3+dFgBLBHyofQam6r4mO195AhsOQKMU+PKX8HNpKpIwjLtIVfilONRGnY9MjCipa4YKD/7ZPeeaN9DYtl4EIHFE4G4024qBumFumgALtqp+Am/eo1qO2wwxsNrMI1epXXvsNlixW7XxKtTvyxMMOfc955573d/ri/HXCwGwRCAA/Bj4Uvc1xV6451/wodVZ6PFRaguyRrJ+oNZhN9Quvb+4WvXtn7FBde/1BnTHfdMfKtjxlHveje/VJ+OvNwJQJidwH/Cp/pAAv/lS7TsQMmHMEDVD0LmlGF1toUGy2hZu7BD17/eXwSuz1PXSNP6SYN7m8e4Ft0ysb8ZfrwSgjAg8iGZDkVLeXqT2ISz2ql5xy56VHYhqAy0bw4cPqrr+YEht0Z2+XHfTDsAMFQdzv33Qs/iuSdSDhF+9F4BSEXCmMR54NZLXzdqoZggO5amGIjOeUC6nUDN0awUTH4Y+HVRp75Ofw8yNEbyBGXIGj6+9x7PspzOpQ+W9IgD6QvA8qmBIeznbtmy4+nW1mCjJDn+9SzUclbxAfBnSWZ33DufBySL4+UcRtPACCAWOB3KW3OZZ/uBC6sCSXhGAqovABOABQDddxMlCVSqcvkz9+7ZhsPRZqRyMF9cPgHfvV2XbWSfhgQ9g19EI3iDoy/IfnHVzyapfrwS89f181vvNs5xpfAKMi8QN9Afh/yaprsPFXujTHlb8Hu4dLgYaKwxD1WO8erua4994UJ3/CAp8MAMlW307PxjjXff7jSR4Jx8RgOiKwFxgBHAsktdNXQ8jX1eVgw1T4L37Ve15s4ZyTqNJsh1eugV+OVIJwYJt8OgnkS3dNv3Fa3wbXhnr2/b2rkg8PhGA+iMCGcClQES93XcdhRF/gc/XqH/ffjGseQGu7ivnNBq0aATpD8KPLlTZ/Q+Xq+ad3ghM2PS55pasfux2//7Jh8T4RQDOJQKHgMuBiLZ0dnvhVx/Dz/4NTjd0bA7TH4e37pF2Y9WhRxv4+GEY1Em17/rjNPjXsgim+QCzJP8D95xr7wseW32cBG3dLQIQXxFwAqOBiDseTPkOhr8My3YqV/VnI2D18zCij5zXSLmyF0z8udrIJb8YfvEftdVbBARN99E/Fc8Y/hvT5zwlxn+W3EpdbAgSLZqNZzzwNpAU0Uk14KEfwiu3Kg/ANOHrb1VF4fFaWGtWmxqCNEmFR6+B2y5Way8yc+HJL9TGLvrDvlkSKjr4a/fc67+kjjTvjAV1tiNQlEVgBDAFaBXpa7u3VmWqpR6A063KVD9cEUGpaj0RAMOAHw2EJ65XFX6gGnY+Nxncvoju6oKQc8+97vljliCZfhGAKIlAV2AyMKwqN/adl6jVhG2bqv/bdEjtPLvhgAhAqVA+e5Nq4gGqb9+7S9QsS2RCaRwKnsi4zbPknk1Isk8EIMoikAr8A3ikSq9vqJaq/nyEcm1DJny8Cl6eWfNNSGtKABokwyMj4J7hqrLSNFWc/4+FVTonm4NHV93qWfGzLBJwk04RgMQRggeAd4EqzfYP7gwTfhxuN5ZXpETg41U1FxbUhABcPxCeuh5aWxu17j8Br89RTTwi9LEAY37g4Mz7Stb89qSYtQhAPERgADAJ6FeV19sMuP8KeGFsON7ddEitYf8uq24LQI828Lsbw+6+xwfvL4cv16oKy8hs3w6hwHsl/3vq6cDheV4xaRGAeIpAQysk+HlV36NlYyUC91+hRME04bM18PKM+M4WxEMAmqTCw1fB3Zeqjj2gOi5NmA8nqhICGQ4/Qc/TRV8P+BcyxScCUINCcAfwHlWYJSgbFvztHri4m3Vu3fDGPLXgyBdIbAGwGTBuqJraO88KmjJz4f/NrYq7b7n8tuR8gp57iib1XSh3oAhAbRCBtsD7wJiqvodhwE+GwwvjwrMFu4+pabDF2xNTAIZ2hd/coLbjApXd/2AFfLUWglXJdxh2sCXvw+ccUzR54E6580QAapsQ3I8qHGpa5fdoCM+MVs1Gkh3q/xZvV/XvES17rUEBaNcMnrweRvW33t+E6Rnw3hI4VaWPMcCwYyQ1WmYGvbcVfd6lQO42EYDaKgLnA+8AY6vzPr3bwWt3wLWWEQVCMHElvDY7+tOG0RKARslw/5Vw7w8g1RKvjAPw5rzIduQ5zfgdDTAcDT8wUlr8ypVuyPy+CEBCCMEYyxvoUp33uba/WgPfp304PzBhIaQtAY+/dgiA3YBbh8H4keE4/5gT/r4AFlUnfDHsGI5G2SQ1erzwo6bT5a4SAUg0EWiI2qn4aSC5qu/jsMEDV8JzN4enDbPz4U/TYdr66tcPVEcAru4Dv7pWVfOBapDy8Sr4/H9QUp2x2pbsN+wp72DYXij8pF2x3E0iAIksBH2tsGBktd6nITx1HYy/BhpYy5O2HIbnpsDK3fEVgH4d4cnrwvP5wZASo7RlVY3zy7j89pRVBDyPFk3qs0XuHhGAuiQE9wJvAO2r8z7nt4AXb1Fud+luRUt3wIszVEFRLAWgZ1v45TXww15q5gJg6U741xJVzVctDHsuQd8fir7u9xEQkjtGBKAuikBj4HngqeqEBaAaZbx2O1zZu/SCqvblr8+BHUeiKwBdW6kY/5p+YdHZnqPq9qs2n1/O8v2E/GmBnCUvl6x+/CRSyy8CUA+E4AJUJeGPqvteI/vBn8apgiJQOYGZG+Cvc/WE4FwC0LG52nNv9KBwBd/e42rE/2ZPZN15znwXhpaGTu35rXvB2C1IRZ8IQD0UgtGo2YILqjWGGmrrsmdvgn4dwkIwz1pdty4zMgHo2RYeuAJGDQgb/oGTkLYUluyIwsIl0zxoeo7/rnjWiBmodfsy6osA1FsRSAaeRM0YNK7Oe9kMGHsR/N+NYSEAJQDvL4fZG09vqFkqADYDRvSGOy6BS7qHY/ycArXn3n+3RMPwQ27T55rg3fT6hEDW9FMy6osACOHz3x74K3Avap1rtYRg9CB44jq4tEf4/08WqjzBnE2w+bBajdf3vGyu7O7m2v7QpkwN495jMHE1LNpaxdLdivdboGR68MjS50v+91Qm4JdRXwRAOLMQXIZaYDQkGu93aQ8Vx988RG2qUZHs7GzcbhUCBEOwYrdqcPrt/ijE+ACh4PbgqZ2/8yy8bRlQgmT4RQCESkXABjwE/IVqrDQsS6smKk9w02C4sJPqyAOQk5NNxj43y3epdQe50VuKXGB6TrzmWfbTD0Ku/cVIm666IwBC3DgP+APwKNAgFh8Qg9WAJaa/eGIga+rr3g2vHkWac4oACNWmM/A6cHd18wOxEwCbScg7K3BkxZ9LVj26C7X5psT5IgBCFBkK/A24qvYIgAFmcG3Iue959/ybV4vhJ44AOOQ0JBwZwNWoHY1fpYp9CaODDTD3miV5rxbPGD4ZSfAlHLI1WOIyA7gQ1ZPwYPxvG1uO6Xc9VbL6iWHFM4Z/itqBR4w/wZAQoG6QDDwMPAd0iF0IYAPDdpxA8QTvlrfS/bsnusToJQcg1B4aAL8EngVaR08AbGBz5BHw/MOfOeldb8aLBRLjiwAItZfGwOOo8uLWVRMAAwwbGI48giVpwdx1EzzL7s+TUysCICQODVHFRE8D3fQEwACbAwz7YUL+fwZPbnjfs+hOp5xKEQAhcbEDN1nhwSgqJH+VAHjAlhTCsK3ADKX7d0+c7s14UQp46rgAyDRg/SAIzLR+ugC3WYIwCMAw7DtD/sIlpjd/knvOqH2ohTpCPaCcByAIQv1C6gAEQQRAEAQRAEEQRAAEQRABEAShjvP/BwDg2RNMoQwVwQAAAABJRU5ErkJggg==","encoding":"base64"},"redirectURL":null,"headersSize":0,"bodySize":15229},"serverIPAddress":"106.75.214.88","cache":{},"timings":{"dns":-1,"connect":-1,"ssl":-1,"send":1,"wait":36,"receive":46}},{"startedDateTime":"2019-04-19T14:14:27.834+08:00","time":164,"request":{"method":"POST","url":"https://testerhome.com/account/sign_in","httpVersion":"HTTP/1.1","cookies":[{"name":"_ga","value":"GA1.2.162109905.1516957848"},{"name":"gsScrollPos-3842","value":"0"},{"name":"gsScrollPos-3871","value":""},{"name":"gsScrollPos-1094","value":"0"},{"name":"gsScrollPos-1837","value":"0"},{"name":"gsScrollPos-1993","value":""},{"name":"gsScrollPos-324","value":"0"},{"name":"gsScrollPos-861","value":"0"},{"name":"gsScrollPos-1568","value":"0"},{"name":"gsScrollPos-2909","value":"0"},{"name":"gsScrollPos-2429","value":""},{"name":"gsScrollPos-5380","value":"0"},{"name":"gsScrollPos-5417","value":"0"},{"name":"gsScrollPos-1937750581","value":""},{"name":"gsScrollPos-1937751827","value":"0"},{"name":"gsScrollPos-1937751846","value":"0"},{"name":"gsScrollPos-1937756244","value":"0"},{"name":"gsScrollPos-1937759407","value":"0"},{"name":"gsScrollPos-968","value":"0"},{"name":"gsScrollPos-3085","value":""},{"name":"hasSkipWelcomePage","value":"1"},{"name":"gsScrollPos-1874","value":"0"},{"name":"gsScrollPos-2049","value":"0"},{"name":"gsScrollPos-3406","value":""},{"name":"user_id","value":"NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f"},{"name":"gsScrollPos-891","value":"0"},{"name":"gsScrollPos-931","value":""},{"name":"gsScrollPos-2436","value":"0"},{"name":"_gid","value":"GA1.2.113994526.1555584877"},{"name":"_gat","value":"1"},{"name":"_homeland_session","value":"%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D"}],"headers":[{"name":"Host","value":"testerhome.com"},{"name":"Connection","value":"keep-alive"},{"name":"Content-Length","value":"134"},{"name":"Accept","value":"*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},{"name":"Origin","value":"https://testerhome.com"},{"name":"X-CSRF-Token","value":"0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA=="},{"name":"X-Requested-With","value":"XMLHttpRequest"},{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"},{"name":"Content-Type","value":"application/x-www-form-urlencoded; charset=UTF-8"},{"name":"Referer","value":"https://testerhome.com/account/sign_in"},{"name":"Accept-Encoding","value":"gzip, deflate, br"},{"name":"Accept-Language","value":"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"},{"name":"Cookie","value":"_ga=GA1.2.162109905.1516957848; gsScrollPos-3842=0; gsScrollPos-3871=; gsScrollPos-1094=0; gsScrollPos-1837=0; gsScrollPos-1993=; gsScrollPos-324=0; gsScrollPos-861=0; gsScrollPos-1568=0; gsScrollPos-2909=0; gsScrollPos-2429=; gsScrollPos-5380=0; gsScrollPos-5417=0; gsScrollPos-1937750581=; gsScrollPos-1937751827=0; gsScrollPos-1937751846=0; gsScrollPos-1937756244=0; gsScrollPos-1937759407=0; gsScrollPos-968=0; gsScrollPos-3085=; hasSkipWelcomePage=1; gsScrollPos-1874=0; gsScrollPos-2049=0; gsScrollPos-3406=; user_id=NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f; gsScrollPos-891=0; gsScrollPos-931=; gsScrollPos-2436=0; _gid=GA1.2.113994526.1555584877; _gat=1; _homeland_session=%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D"}],"queryString":[],"postData":{"mimeType":"application/x-www-form-urlencoded; charset=UTF-8","params":[{"name":"utf8","value":"✓"},{"name":"user[login]","value":"debugtalk"},{"name":"user[password]","value":"LongForEver"},{"name":"user[remember_me]","value":"0"},{"name":"user[remember_me]","value":"1"},{"name":"commit","value":"Sign In"}]},"headersSize":1796,"bodySize":134},"response":{"_charlesStatus":"COMPLETE","status":200,"statusText":"OK","httpVersion":"HTTP/1.1","cookies":[{"name":"remember_user_token","value":"eyJfcmFpbHMiOnsibWVzc2FnZSI6IlcxczJNVEE1WFN3aUpESmhKREV3SkRkbEwwWTVTREZXTm1waFREaG1lR1V1ZEVocFJFOGlMQ0l4TlRVMU5qVTBORFk1TGprNU1qUTJNalFpWFE9PSIsImV4cCI6IjIwMTktMDYtMTlUMDY6MTQ6MjkuOTkyWiIsInB1ciI6bnVsbH19--343d68b57a6619d293f944bfbf63fa557e5a0dd8","path":"/","domain":null,"expires":"Wed, 19 Jun 2019 06:14:29 -0000","httpOnly":true,"secure":true,"comment":null,"_maxAge":null},{"name":"_homeland_session","value":"PbiWoS7j4O3tTBlLCp9RoWTpYGmGCCJWcCjXhgTJWqCajO540nkdCTYA9PmiCneuZcJm4L7VYhPxnE%2FsQ1g%2BBPzdkzFkkpHT0FF8Htn8iWolqAiK3VNoeUz5EZEVLlrrSqVOoXLjYvylSMoWK624mba%2F4Fkg3E2uXqq4G1jRISyOAr1RtvMwYEo8U1Vo2TLFNNxDeyVM3IEqXOprE3M1nlP9qpeHPDnmWdWyRoiNo5MGIAkaReJIeoxECjdgts3sqYsqpWGuh0lJjdtIbmXNlBap8vU%2B54X7x6h9%2B0s%3D--KR8JpaHy431CsKRy--ueD9I5%2B%2FH%2BlH5vq1eUq4cg%3D%3D","path":"/","domain":null,"expires":"Thu, 18 Jul 2019 06:14:30 -0000","httpOnly":true,"secure":true,"comment":null,"_maxAge":null}],"headers":[{"name":"Server","value":"nginx/1.10.2"},{"name":"Date","value":"Fri, 19 Apr 2019 06:14:30 GMT"},{"name":"Content-Type","value":"text/javascript; charset=utf-8"},{"name":"Transfer-Encoding","value":"chunked"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Frame-Options","value":"SAMEORIGIN"},{"name":"X-XSS-Protection","value":"1; mode=block"},{"name":"X-Content-Type-Options","value":"nosniff"},{"name":"X-Download-Options","value":"noopen"},{"name":"X-Permitted-Cross-Domain-Policies","value":"none"},{"name":"Referrer-Policy","value":"strict-origin-when-cross-origin"},{"name":"Location","value":"https://testerhome.com/"},{"name":"ETag","value":"W/\"d2d52e29771efe4882102d03bcbbc5ff\""},{"name":"Cache-Control","value":"max-age=0, private, must-revalidate"},{"name":"Set-Cookie","value":"remember_user_token=eyJfcmFpbHMiOnsibWVzc2FnZSI6IlcxczJNVEE1WFN3aUpESmhKREV3SkRkbEwwWTVTREZXTm1waFREaG1lR1V1ZEVocFJFOGlMQ0l4TlRVMU5qVTBORFk1TGprNU1qUTJNalFpWFE9PSIsImV4cCI6IjIwMTktMDYtMTlUMDY6MTQ6MjkuOTkyWiIsInB1ciI6bnVsbH19--343d68b57a6619d293f944bfbf63fa557e5a0dd8; path=/; expires=Wed, 19 Jun 2019 06:14:29 -0000; secure; HttpOnly"},{"name":"Set-Cookie","value":"_homeland_session=PbiWoS7j4O3tTBlLCp9RoWTpYGmGCCJWcCjXhgTJWqCajO540nkdCTYA9PmiCneuZcJm4L7VYhPxnE%2FsQ1g%2BBPzdkzFkkpHT0FF8Htn8iWolqAiK3VNoeUz5EZEVLlrrSqVOoXLjYvylSMoWK624mba%2F4Fkg3E2uXqq4G1jRISyOAr1RtvMwYEo8U1Vo2TLFNNxDeyVM3IEqXOprE3M1nlP9qpeHPDnmWdWyRoiNo5MGIAkaReJIeoxECjdgts3sqYsqpWGuh0lJjdtIbmXNlBap8vU%2B54X7x6h9%2B0s%3D--KR8JpaHy431CsKRy--ueD9I5%2B%2FH%2BlH5vq1eUq4cg%3D%3D; path=/; expires=Thu, 18 Jul 2019 06:14:30 -0000; secure; HttpOnly"},{"name":"X-Request-Id","value":"149b5636-67ef-4ea6-825b-6894894c1e97"},{"name":"X-Runtime","value":"0.124799"},{"name":"Strict-Transport-Security","value":"max-age=15552000; includeSubDomains"},{"name":"Content-Encoding","value":"gzip"},{"name":"Connection","value":"keep-alive"}],"content":{"size":89,"mimeType":"text/javascript; charset=utf-8","text":"Turbolinks.clearCache()\nTurbolinks.visit(\"https://testerhome.com/\", {\"action\":\"replace\"})"},"redirectURL":"https://testerhome.com/","headersSize":0,"bodySize":109},"serverIPAddress":"106.75.214.88","cache":{},"timings":{"dns":-1,"connect":-1,"ssl":-1,"send":1,"wait":162,"receive":1}},{"startedDateTime":"2019-04-19T14:14:28.011+08:00","time":158,"request":{"method":"GET","url":"https://testerhome.com/","httpVersion":"HTTP/1.1","cookies":[{"name":"_ga","value":"GA1.2.162109905.1516957848"},{"name":"gsScrollPos-3842","value":"0"},{"name":"gsScrollPos-3871","value":""},{"name":"gsScrollPos-1094","value":"0"},{"name":"gsScrollPos-1837","value":"0"},{"name":"gsScrollPos-1993","value":""},{"name":"gsScrollPos-324","value":"0"},{"name":"gsScrollPos-861","value":"0"},{"name":"gsScrollPos-1568","value":"0"},{"name":"gsScrollPos-2909","value":"0"},{"name":"gsScrollPos-2429","value":""},{"name":"gsScrollPos-5380","value":"0"},{"name":"gsScrollPos-5417","value":"0"},{"name":"gsScrollPos-1937750581","value":""},{"name":"gsScrollPos-1937751827","value":"0"},{"name":"gsScrollPos-1937751846","value":"0"},{"name":"gsScrollPos-1937756244","value":"0"},{"name":"gsScrollPos-1937759407","value":"0"},{"name":"gsScrollPos-968","value":"0"},{"name":"gsScrollPos-3085","value":""},{"name":"hasSkipWelcomePage","value":"1"},{"name":"gsScrollPos-1874","value":"0"},{"name":"gsScrollPos-2049","value":"0"},{"name":"gsScrollPos-3406","value":""},{"name":"user_id","value":"NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f"},{"name":"gsScrollPos-891","value":"0"},{"name":"gsScrollPos-931","value":""},{"name":"gsScrollPos-2436","value":"0"},{"name":"_gid","value":"GA1.2.113994526.1555584877"},{"name":"_gat","value":"1"},{"name":"remember_user_token","value":"eyJfcmFpbHMiOnsibWVzc2FnZSI6IlcxczJNVEE1WFN3aUpESmhKREV3SkRkbEwwWTVTREZXTm1waFREaG1lR1V1ZEVocFJFOGlMQ0l4TlRVMU5qVTBORFk1TGprNU1qUTJNalFpWFE9PSIsImV4cCI6IjIwMTktMDYtMTlUMDY6MTQ6MjkuOTkyWiIsInB1ciI6bnVsbH19--343d68b57a6619d293f944bfbf63fa557e5a0dd8"},{"name":"_homeland_session","value":"PbiWoS7j4O3tTBlLCp9RoWTpYGmGCCJWcCjXhgTJWqCajO540nkdCTYA9PmiCneuZcJm4L7VYhPxnE%2FsQ1g%2BBPzdkzFkkpHT0FF8Htn8iWolqAiK3VNoeUz5EZEVLlrrSqVOoXLjYvylSMoWK624mba%2F4Fkg3E2uXqq4G1jRISyOAr1RtvMwYEo8U1Vo2TLFNNxDeyVM3IEqXOprE3M1nlP9qpeHPDnmWdWyRoiNo5MGIAkaReJIeoxECjdgts3sqYsqpWGuh0lJjdtIbmXNlBap8vU%2B54X7x6h9%2B0s%3D--KR8JpaHy431CsKRy--ueD9I5%2B%2FH%2BlH5vq1eUq4cg%3D%3D"}],"headers":[{"name":"Host","value":"testerhome.com"},{"name":"Connection","value":"keep-alive"},{"name":"Accept","value":"text/html, application/xhtml+xml"},{"name":"Turbolinks-Referrer","value":"https://testerhome.com/account/sign_in"},{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"},{"name":"Referer","value":"https://testerhome.com/account/sign_in"},{"name":"Accept-Encoding","value":"gzip, deflate, br"},{"name":"Accept-Language","value":"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"},{"name":"Cookie","value":"_ga=GA1.2.162109905.1516957848; gsScrollPos-3842=0; gsScrollPos-3871=; gsScrollPos-1094=0; gsScrollPos-1837=0; gsScrollPos-1993=; gsScrollPos-324=0; gsScrollPos-861=0; gsScrollPos-1568=0; gsScrollPos-2909=0; gsScrollPos-2429=; gsScrollPos-5380=0; gsScrollPos-5417=0; gsScrollPos-1937750581=; gsScrollPos-1937751827=0; gsScrollPos-1937751846=0; gsScrollPos-1937756244=0; gsScrollPos-1937759407=0; gsScrollPos-968=0; gsScrollPos-3085=; hasSkipWelcomePage=1; gsScrollPos-1874=0; gsScrollPos-2049=0; gsScrollPos-3406=; user_id=NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f; gsScrollPos-891=0; gsScrollPos-931=; gsScrollPos-2436=0; _gid=GA1.2.113994526.1555584877; _gat=1; remember_user_token=eyJfcmFpbHMiOnsibWVzc2FnZSI6IlcxczJNVEE1WFN3aUpESmhKREV3SkRkbEwwWTVTREZXTm1waFREaG1lR1V1ZEVocFJFOGlMQ0l4TlRVMU5qVTBORFk1TGprNU1qUTJNalFpWFE9PSIsImV4cCI6IjIwMTktMDYtMTlUMDY6MTQ6MjkuOTkyWiIsInB1ciI6bnVsbH19--343d68b57a6619d293f944bfbf63fa557e5a0dd8; _homeland_session=PbiWoS7j4O3tTBlLCp9RoWTpYGmGCCJWcCjXhgTJWqCajO540nkdCTYA9PmiCneuZcJm4L7VYhPxnE%2FsQ1g%2BBPzdkzFkkpHT0FF8Htn8iWolqAiK3VNoeUz5EZEVLlrrSqVOoXLjYvylSMoWK624mba%2F4Fkg3E2uXqq4G1jRISyOAr1RtvMwYEo8U1Vo2TLFNNxDeyVM3IEqXOprE3M1nlP9qpeHPDnmWdWyRoiNo5MGIAkaReJIeoxECjdgts3sqYsqpWGuh0lJjdtIbmXNlBap8vU%2B54X7x6h9%2B0s%3D--KR8JpaHy431CsKRy--ueD9I5%2B%2FH%2BlH5vq1eUq4cg%3D%3D"},{"name":"If-None-Match","value":"W/\"bad62c68dac27b01151516aad5c7f0be\""}],"queryString":[],"headersSize":1829,"bodySize":0},"response":{"_charlesStatus":"COMPLETE","status":200,"statusText":"OK","httpVersion":"HTTP/1.1","cookies":[{"name":"_homeland_session","value":"2H2EpF3BCnG%2FjK7qt8ZryfK3zYKsTqxtfoXvNeb7oYUNjCHZr3lV2lIhH4D6KZ1SWDEgItreKFMI2f%2FuR8UE5Iy1P9VSXrPhtRuMxGQnshQgAftJ83KwBriYXSA0MdRuqEE1%2B5nJxDNbnN5sxwsyVCQZe6x5GV%2FCice0d4bczcrV744Hngp1licLOa29YPMpHy5CbU4mcLNRxi%2Bx2f%2B%2B86Lw7d16u10Dake2OZNVjksDTGJYxF9jXQzNNqF2ZUSSq202gB2xUjME4ZURDdcrTE6bIp66KULllVOWmHTkvuBtJA3G6DyZhqoD5gZ5OXZYzgY%2FRQm7u1GeJrNRquJKmZ9u6%2FxIiDF%2FxHjZv%2Fb%2FptJQWVwm2MfCFbnR1bKSfQyi3WnrBkylE84Fk5hFQw%3D%3D--B5BuYCY2BWs3sTg%2B--e9ZCJstvL2HPacQ1gd51sQ%3D%3D","path":"/","domain":null,"expires":"Thu, 18 Jul 2019 06:14:30 -0000","httpOnly":true,"secure":true,"comment":null,"_maxAge":null}],"headers":[{"name":"Server","value":"nginx/1.10.2"},{"name":"Date","value":"Fri, 19 Apr 2019 06:14:30 GMT"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Transfer-Encoding","value":"chunked"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Frame-Options","value":"SAMEORIGIN"},{"name":"X-XSS-Protection","value":"1; mode=block"},{"name":"X-Content-Type-Options","value":"nosniff"},{"name":"X-Download-Options","value":"noopen"},{"name":"X-Permitted-Cross-Domain-Policies","value":"none"},{"name":"Referrer-Policy","value":"strict-origin-when-cross-origin"},{"name":"ETag","value":"W/\"d5e2bdc2e5b529a1b78123e327656514\""},{"name":"Cache-Control","value":"max-age=0, private, must-revalidate"},{"name":"Content-Security-Policy","value":";"},{"name":"Set-Cookie","value":"_homeland_session=2H2EpF3BCnG%2FjK7qt8ZryfK3zYKsTqxtfoXvNeb7oYUNjCHZr3lV2lIhH4D6KZ1SWDEgItreKFMI2f%2FuR8UE5Iy1P9VSXrPhtRuMxGQnshQgAftJ83KwBriYXSA0MdRuqEE1%2B5nJxDNbnN5sxwsyVCQZe6x5GV%2FCice0d4bczcrV744Hngp1licLOa29YPMpHy5CbU4mcLNRxi%2Bx2f%2B%2B86Lw7d16u10Dake2OZNVjksDTGJYxF9jXQzNNqF2ZUSSq202gB2xUjME4ZURDdcrTE6bIp66KULllVOWmHTkvuBtJA3G6DyZhqoD5gZ5OXZYzgY%2FRQm7u1GeJrNRquJKmZ9u6%2FxIiDF%2FxHjZv%2Fb%2FptJQWVwm2MfCFbnR1bKSfQyi3WnrBkylE84Fk5hFQw%3D%3D--B5BuYCY2BWs3sTg%2B--e9ZCJstvL2HPacQ1gd51sQ%3D%3D; path=/; expires=Thu, 18 Jul 2019 06:14:30 -0000; secure; HttpOnly"},{"name":"X-Request-Id","value":"bfe4bffb-f4f5-4b94-b381-8799f7a029bd"},{"name":"X-Runtime","value":"0.114561"},{"name":"Strict-Transport-Security","value":"max-age=15552000; includeSubDomains"},{"name":"Content-Encoding","value":"gzip"},{"name":"Connection","value":"keep-alive"}],"content":{"size":54096,"compression":42723,"mimeType":"text/html; charset=utf-8","text":"<!--\n _ _ _ _\n | | | | | | | |\n | |_| | ___ _ __ ___ ___| | __ _ _ __ __| |\n | _ |/ _ \\| '_ ` _ \\ / _ \\ |/ _` | '_ \\ / _` |\n | | | | (_) | | | | | | __/ | (_| | | | | (_| |\n \\_| |_/\\___/|_| |_| |_|\\___|_|\\__,_|_| |_|\\__,_|\n ------------------------------------------------\n https://gethomeland.com\n\n - Ruby: 2.4.0-p0\n - Rails: 5.2.0.rc1\n - Homeland: 3.1.0.beta9\n-->\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset='utf-8'/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"/>\n <meta name=\"keywords\" content=\"移动测试,游戏测试,性能测试,软件测试,软件测试社区,软件测试资料,软件测试工具,软件测试报告,软件测试方法,自动化测试,软件测试招聘,\">\n <meta name=\"description\" content=\"TesterHome软件测试社区,人气最旺的软件测试技术门户,提供软件测试社区交流,测试沙龙。\">\n <title>TesterHome<\/title>\n <link rel=\"icon\" href=\"/assets/favicon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"apple-touch-icon-precomposed\" href=\"/assets/ios-icon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"shortcut icon\" href=\"/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\"/>\n <link rel=\"apple-touch-icon\" href=\"/assets/favicon-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png\">\n\n <!-- http://www.favicon-generator.org/ -->\n <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-icon-57x57.png\">\n <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"/apple-icon-60x60.png\">\n <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-icon-72x72.png\">\n <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/apple-icon-76x76.png\">\n <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-icon-114x114.png\">\n <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"/apple-icon-120x120.png\">\n <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-icon-144x144.png\">\n <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"/apple-icon-152x152.png\">\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-icon-180x180.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/android-icon-192x192.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/favicon-96x96.png\">\n <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n <link rel=\"manifest\" href=\"/manifest.json\">\n <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n <meta name=\"msapplication-TileImage\" content=\"/ms-icon-144x144.png\">\n <meta name=\"theme-color\" content=\"#ffffff\">\n\n <meta name=\"apple-mobile-web-app-capable\" content=\"no\">\n <meta content='True' name='HandheldFriendly'/>\n <link rel=\"alternate\" type=\"application/rss+xml\" title=\"订阅最新帖\" href=\"https://testerhome.com/topics/feed\"/>\n <link rel=\"stylesheet\" media=\"screen\" href=\"/assets/front-15185e3983cd68677b04329670c6a6b7fedecb51f1b39ff7fac48180c1850eaa.css\" data-turbolinks-track=\"reload\" />\n \n \n <meta name=\"action-cable-url\" content=\"/cable\" />\n <meta name=\"csrf-param\" content=\"authenticity_token\" />\n<meta name=\"csrf-token\" content=\"jmdCmpEL4XJ0dsx9KfGu5m2XI1rDE5wBYn7FmGqzPss2kszANWkmYblmvJtqydpgdWrDQktsHETJYVo+BER5Ww==\" />\n <meta itemprop=\"name\" content=\"TesterHome\"/>\r\n <meta itemprop=\"image\" content=\"//10.url.cn/qqcourse_logo_ng/ajNVdqHZLLAH8YXbXMDFib2SnIqhac60vw3BspyLd0TO82PiaJ2xfbvXynrocic7ajbCGribAic88wgc/\"/>\r\n\r\n<style type=\"text/css\">\r\n\r\n.blog-description{\r\n text-align: left;\r\n padding: 18px;\r\n}\r\n.blog-description:first-letter {\r\nfont-size : 200%;\r\nfont-weight : bold;\r\nfloat : left;\r\nmargin-right: 3px;\r\n}\r\n\r\n/*\r\n.new-topic:before {\r\n content:url(\"https://testerhome.com/uploads/photo/2019/d80c7d7c-7727-45b2-a98d-2c87a68c65e8.png\");\r\n position: absolute;\r\n left:60%;\r\n margin-top:-35px;\r\n}\r\n*/\r\n<\/style>\n <meta name=\"current-user\" data-user-id=\"6109\" data-user-login=\"debugtalk\" data-user-name=\"debugtalk\" data-user-email=\"mail@debugtalk.com\" data-user-avatar-url=\"/uploads/user/avatar/6109.jpg!md\">\n\n <script src=\"/assets/app-04f83676fc1f6160a4410556b3298892a4536d9e8ee6c9b54df9191dd8dd9060.js\" data-turbolinks-track=\"reload\"><\/script>\n \n<\/head>\n<body class=\"page-home\" data-controller-name=\"home\">\n\n <div id=\"welcome-page\" class=\"hide-page\">\n <div \r\nstyle=\"background-image: url(https://testerhome.com/uploads/photo/2019/971bc4ea-b6eb-4d64-b6a1-1e71928314af.jpg); background-position: top center; background-attachment: fixed; background-size: cover; background-repeat: no-repeat; height:100%\">\r\n<\/div>\n <div class=\"welcome-action-button\">\n <div class=\"btn-group\">\n <a id=\"welcome-page-skip\" type=\"button\" class=\"btn btn-primary\">跳过<\/a>\n <\/div>\n <div class=\"btn-group\">\n <a href=\"http://2019.test-china.org/\" type=\"button\" class=\"btn btn-info\">查看详情<\/a>\n <\/div>\n <\/div>\n <\/div>\n\n <div id=\"main-page\">\n<div class=\"header\">\n <nav class=\"navbar navbar-inverse navbar-fixed-top navbar-default\">\n <div class=\"container\">\n <div class=\"navbar-header\" id=\"navbar-header\" data-turbolinks-permanent>\n <button type=\"button\" class=\"navbar-toggle collapsed\" data-toggle=\"collapse\" data-target=\"#main-navbar-collapse\">\n <span class=\"sr-only\">Toggle<\/span>\n <i class=\"fa fa-reorder\"><\/i>\n <\/button>\n <a href=\"/\" class=\"navbar-brand\"><b>TesterHome<\/b><\/a>\n\n <\/div>\n <div class=\"collapse navbar-collapse\" id=\"main-navbar-collapse\">\n \n <div id=\"main-nav-menu\">\n <ul class=\"nav navbar-nav\">\n <li class=\"\"><a href=\"/topics\">Topics<\/a><\/li><li class=\"\"><a href=\"/bugs\">Bug Tracker<\/a><\/li><li class=\"\"><a href=\"/questions\">QA<\/a><\/li><li class=\"\"><a href=\"/teams\">Teams<\/a><\/li><li class=\"\"><a href=\"/jobs\">招聘<\/a><\/li><li class=\"\"><a href=\"/wiki\">Wiki<\/a><\/li><li class=\"\"><a href=\"/opensource_projects\">开源项目<span class=\"badge-new\">新<\/span><\/a><\/li>\n \n <\/ul>\n<\/div>\n <\/div>\n <ul class=\"nav user-bar navbar-nav navbar-right\">\n <li class=\"dropdown dropdown-avatar\">\n <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-expanded=\"false\">\n <img class=\"media-object avatar-32\" src=\"/uploads/user/avatar/6109.jpg!sm\" /> <span class=\"caret\"><\/span>\n <\/a>\n <button class=\"navbar-toggle\" type=\"button\" data-toggle=\"dropdown\" role=\"button\" aria-expanded=\"false\">\n <span class=\"sr-only\">Toggle<\/span>\n <img class=\"media-object avatar-32\" src=\"/uploads/user/avatar/6109.jpg!sm\" />\n <\/button>\n <ul class=\"dropdown-menu\" role=\"menu\"><li class=\"\"><a href=\"/debugtalk\">debugtalk<\/a><\/li><li class=\"\"><div class='divider'><\/div><\/li><li class=\"\"><a href=\"/setting\">Account Profile<\/a><\/li><li class=\"\"><a href=\"/debugtalk/columns\">我的专栏<\/a><\/li><li class=\"\"><a href=\"/topics/favorites\">Favorites<\/a><\/li><li class=\"\"><a href=\"/notes\">记事本<\/a><\/li><li class=\"\"><div class='divider'><\/div><\/li><li class=\"\"><a rel=\"nofollow\" data-method=\"delete\" href=\"/account/sign_out\">Sign Out<\/a><\/li><\/ul>\n <\/li>\n<\/ul>\n\n<ul class=\"nav navbar-nav user-bar navbar-right\">\n <li class=\"nav-search hidden-xs hidden-sm hidden-md\">\n <form class=\"navbar-form form-search active\" action=\"/search\" method=\"GET\">\n <div class=\"form-group\">\n <input class=\"form-control\" name=\"q\" type=\"text\" value=\"\" placeholder=\"搜索本站内容\" />\n <\/div>\n <i class=\"fa btn-search fa-search\"><\/i>\n <\/form>\n <\/li>\n\n\n <li class=\"visible-xs\">\n <a href=\"/topics/new\" title=\"New Topic\"><i class=\"fa fa-edit\"><\/i><\/a>\n <\/li>\n\n <li class=\"notification-count hidden-sm\">\n <a href=\"/notifications\" class=\"\" title=\"通知\"><i class=\"fa fa-bell\"><\/i><span class=\"count\">0<\/span><\/a>\n <\/li>\n <li class=\"hidden-sm\">\n <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-expanded=\"false\">\n <i class=\"fa fa-plus\"><\/i> <span class=\"caret\"><\/span>\n <\/a>\n <ul class=\"dropdown-menu\" role=\"menu\"><li class=\"\"><a href=\"/topics/new\">New Topic<\/a><\/li><li class=\"\"><div class='divider'><\/div><\/li><li class=\"\"><a href=\"/teams/new\">New team<\/a><\/li><\/ul>\n <\/li>\n<\/ul>\n\n <\/div>\n <\/nav>\n <div id=\"corner\" class=\"\">\n <a id=\"cornertip\" href=\"\">\n <div id=\"c-content\">\n <div id=\"c-button\">欢迎<\/div>\n <\/div>\n <\/a>\n <\/div>\n<\/div>\n\n\n\n<div id=\"main\" class=\"main-container container\">\n \n \n \n\n\n<div class=\"row\">\n <div class=\"col-md-9 home-main\">\n \n \n<div class=\"home_suggest_topics panel panel-default\">\n <div class=\"panel-heading\">社区置顶<\/div>\n <div class=\"panel-body topics row\">\n <div class=\"col-md-6\">\n <div class=\"topic media topic-18791\">\n <div class=\"avatar media-left\">\n <a title=\"kasi (卡斯)\" href=\"/kasi\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/441/ee76af.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"TesterHome 深圳沙龙议题征集\" href=\"/topics/18791\">TesterHome 深圳沙龙议题征集<\/a>\n <i class=\"fa fa-thumb-tack\" title=\"置顶\"><\/i>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"卡斯\" title=\"卡斯(kasi)\" href=\"/kasi\">卡斯<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <\/div>\n<\/div>\n<div class=\"topic media topic-18682\">\n <div class=\"avatar media-left\">\n <a title=\"chenhengjie123 (陈恒捷)\" href=\"/chenhengjie123\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/605.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"TesterHome 广州沙龙 2019年 第 1 期报名中!\" href=\"/topics/18682\">TesterHome 广州沙龙 2019年 第 1 期报名中!<\/a>\n <i class=\"fa fa-thumb-tack\" title=\"置顶\"><\/i>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"陈恒捷\" title=\"陈恒捷(chenhengjie123)\" href=\"/chenhengjie123\">陈恒捷<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18682#reply-141543\">8<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"col-md-6\">\n <div class=\"topic media topic-18683\">\n <div class=\"avatar media-left\">\n <a title=\"LMD (Raymond)\" href=\"/LMD\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/22711/395fcb.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"QA 最佳实践:大厂如何提升软件质量?|福利\" href=\"/topics/18683\">QA 最佳实践:大厂如何提升软件质量?|福利<\/a>\n <i class=\"fa fa-thumb-tack\" title=\"置顶\"><\/i>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"Raymond\" title=\"Raymond(LMD)\" href=\"/LMD\">Raymond<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18683#reply-141529\">4<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17705\">\n <div class=\"avatar media-left\">\n <a title=\"TesterHome (TesterHome)\" href=\"/TesterHome\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/555/652855.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"2019 中国移动互联网测试大会议题征集\" href=\"/topics/17705\">2019 中国移动互联网测试大会议题征集<\/a>\n <i class=\"fa fa-thumb-tack\" title=\"置顶\"><\/i>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"TesterHome\" title=\"TesterHome(TesterHome)\" href=\"/TesterHome\">TesterHome<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17705#reply-140118\">21<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <\/div>\n<\/div>\n\n\n<div class=\"home_suggest_topics panel panel-default\">\n <div class=\"panel-heading\">\n 最新帖子\n <div class=\"pull-right\">\n <a href=\"/topics/last\">查看更多...<\/a>\n <\/div>\n <\/div>\n <div class=\"panel-body topics row\">\n <div class=\"col-md-6\">\n <div class=\"topic media topic-18830\">\n <div class=\"avatar media-left\">\n <a title=\"debugtalk (debugtalk)\" href=\"/debugtalk\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/6109.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"HttpRunner 的测试用例分层机制 (适用于 2.X)\" href=\"/articles/18830\">HttpRunner 的测试用例分层机制 (适用于 2.X)<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"debugtalk\" title=\"debugtalk(debugtalk)\" href=\"/debugtalk\">debugtalk<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18830#reply-141527\">2<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18825\">\n <div class=\"avatar media-left\">\n <a title=\"fengzhaoxi (dishierjie)\" href=\"/fengzhaoxi\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/41499/2ec04d.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"求助,pytest+allure 报错\" href=\"/topics/18825\">求助,pytest+allure 报错<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"dishierjie\" title=\"dishierjie(fengzhaoxi)\" href=\"/fengzhaoxi\">dishierjie<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18825#reply-141549\">2<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18823\">\n <div class=\"avatar media-left\">\n <a title=\"Anitalisk (llsskkii)\" href=\"/Anitalisk\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/42652/d49ba5.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"[深圳] 字节跳动头条研发团队招聘大量测试开发工程师\" href=\"/topics/18823\">[深圳] 字节跳动头条研发团队招聘大量测试开发工程师<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"llsskkii\" title=\"llsskkii(Anitalisk)\" href=\"/Anitalisk\">llsskkii<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18823#reply-141508\">2<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"col-md-6\">\n <div class=\"topic media topic-18837\">\n <div class=\"avatar media-left\">\n <a title=\"a861357276 (吴先森)\" href=\"/a861357276\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/40667/05fb6a.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"软件产品出了问题,就一定是测试的工作没做足,你怎么看\" href=\"/topics/18837\">软件产品出了问题,就一定是测试的工作没做足,你怎么看<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"吴先森\" title=\"吴先森(a861357276)\" href=\"/a861357276\">吴先森<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18837#reply-141561\">1<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18828\">\n <div class=\"avatar media-left\">\n <a title=\"kasijia (kasijia)\" href=\"/kasijia\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/19929/afdd72.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"[已解决] Appium 启动 iOS 真机 app 后无限重启\" href=\"/topics/18828\">[已解决] Appium 启动 iOS 真机 app 后无限重启<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"kasijia\" title=\"kasijia(kasijia)\" href=\"/kasijia\">kasijia<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18828#reply-141556\">3<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18824\">\n <div class=\"avatar media-left\">\n <a title=\"slowchen (慢慢慢慢热)\" href=\"/slowchen\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/28176/ebf091.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"请教一个 Jenkins 发送邮件带附件的问题\" href=\"/topics/18824\">请教一个 Jenkins 发送邮件带附件的问题<\/a>\n \n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"慢慢慢慢热\" title=\"慢慢慢慢热(slowchen)\" href=\"/slowchen\">慢慢慢慢热<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18824#reply-141528\">7<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <\/div>\n<\/div>\n\n<div class=\"home_suggest_topics panel panel-default\">\n <div class=\"panel-heading\">\n 社区精华帖\n <div class=\"pull-right\">\n <a href=\"/topics/excellent\">查看更多精华帖...<\/a>\n <\/div>\n <\/div>\n <div class=\"panel-body topics row\">\n <div class=\"col-md-6 topics-group\">\n <div class=\"topic media topic-18475\">\n <div class=\"avatar media-left\">\n <a title=\"Never_More (Never_More)\" href=\"/Never_More\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/11427.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"基于模型的测试 (Model-based Testing),希望大家能给一些建议\" href=\"/topics/18475\">基于模型的测试 (Model-based Testing),希望大家能给一些建议<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"Never_More\" title=\"Never_More(Never_More)\" href=\"/Never_More\">Never_More<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18475#reply-141489\">26<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18448\">\n <div class=\"avatar media-left\">\n <a title=\"codeskyblue (codeskyblue)\" href=\"/codeskyblue\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/6853/28cf21.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"iOS 远程真机方案整理\" href=\"/topics/18448\">iOS 远程真机方案整理<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"codeskyblue\" title=\"codeskyblue(codeskyblue)\" href=\"/codeskyblue\">codeskyblue<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18448#reply-139214\">14<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18141\">\n <div class=\"avatar media-left\">\n <a title=\"RealLau (非洲赵子龙)\" href=\"/RealLau\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/18875/0ee193.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"强大的全新 Web UI 测试框架 Cypress - 初尝甜头\" href=\"/topics/18141\">强大的全新 Web UI 测试框架 Cypress - 初尝甜头<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"非洲赵子龙\" title=\"非洲赵子龙(RealLau)\" href=\"/RealLau\">非洲赵子龙<\/a>\n for <a class=\"team-name\" data-name=\"Cypress\" title=\"Cypress(CypressChina)\" href=\"/CypressChina\">Cypress<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18141#reply-141263\">36<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18047\">\n <div class=\"avatar media-left\">\n <a title=\"rodman1985 (江城子)\" href=\"/rodman1985\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/5895.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"一九得九,依旧得酒!-- 我的测试总结和展望\" href=\"/topics/18047\">一九得九,依旧得酒!-- 我的测试总结和展望<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"江城子\" title=\"江城子(rodman1985)\" href=\"/rodman1985\">江城子<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18047#reply-139366\">61<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17764\">\n <div class=\"avatar media-left\">\n <a title=\"debugtalk (debugtalk)\" href=\"/debugtalk\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/6109.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"如何度量测试开发的价值产出?\" href=\"/topics/17764\">如何度量测试开发的价值产出?<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"debugtalk\" title=\"debugtalk(debugtalk)\" href=\"/debugtalk\">debugtalk<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17764#reply-141214\">50<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17646\">\n <div class=\"avatar media-left\">\n <a title=\"simple\" href=\"/simple\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/50.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"[精彩盘点] TesterHome 社区 2018年 度精华帖\" href=\"/topics/17646\">[精彩盘点] TesterHome 社区 2018年 度精华帖<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"simple\" title=\"simple(simple)\" href=\"/simple\">simple<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17646#reply-137269\">29<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17540\">\n <div class=\"avatar media-left\">\n <a title=\"youzancoder (有赞测试团队)\" href=\"/youzancoder\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/38409/c59d23.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"资损防控体系介绍\" href=\"/articles/17540\">资损防控体系介绍<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"有赞测试团队\" title=\"有赞测试团队(youzancoder)\" href=\"/youzancoder\">有赞测试团队<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17540#reply-135391\">6<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17292\">\n <div class=\"avatar media-left\">\n <a title=\"xinxi1990 (xinxi)\" href=\"/xinxi1990\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/26433/ebf4e4.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"使用 uiautomator2+pytest+allure 进行 Android 的 UI 自动化测试\" href=\"/topics/17292\">使用 uiautomator2+pytest+allure 进行 Android 的 UI 自动化测试<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"xinxi\" title=\"xinxi(xinxi1990)\" href=\"/xinxi1990\">xinxi<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17292#reply-139303\">35<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17232\">\n <div class=\"avatar media-left\">\n <a title=\"xinxi1990 (xinxi)\" href=\"/xinxi1990\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/26433/ebf4e4.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"iOS 启动时间测试\" href=\"/topics/17232\">iOS 启动时间测试<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"xinxi\" title=\"xinxi(xinxi1990)\" href=\"/xinxi1990\">xinxi<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17232#reply-138912\">10<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17117\">\n <div class=\"avatar media-left\">\n <a title=\"xinxi1990 (xinxi)\" href=\"/xinxi1990\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/26433/ebf4e4.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"SonarQube 的安装与使用\" href=\"/topics/17117\">SonarQube 的安装与使用<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"xinxi\" title=\"xinxi(xinxi1990)\" href=\"/xinxi1990\">xinxi<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17117#reply-129553\">13<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <div class=\"col-md-6 topics-group\">\n <div class=\"topic media topic-18494\">\n <div class=\"avatar media-left\">\n <a title=\"hlylearner (大豆子)\" href=\"/hlylearner\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/41005/cb690e.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"深入了解 sonar 自定义规则开发 (入门强烈推荐)\" href=\"/topics/18494\">深入了解 sonar 自定义规则开发 (入门强烈推荐)<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"大豆子\" title=\"大豆子(hlylearner)\" href=\"/hlylearner\">大豆子<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18494#reply-139963\">9<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18460\">\n <div class=\"avatar media-left\">\n <a title=\"fenny.ren (fenny)\" href=\"/fenny.ren\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/38212/6e7237.jpeg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"读阿里《不止代码》\u2014\u2014面试\" href=\"/topics/18460\">读阿里《不止代码》\u2014\u2014面试<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"fenny\" title=\"fenny(fenny.ren)\" href=\"/fenny.ren\">fenny<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18460#reply-141436\">13<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18171\">\n <div class=\"avatar media-left\">\n <a title=\"chenhengjie123 (陈恒捷)\" href=\"/chenhengjie123\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/605.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"谷歌开源模糊测试工具 ClusterFuzz 尝鲜记录 (进行中)\" href=\"/topics/18171\">谷歌开源模糊测试工具 ClusterFuzz 尝鲜记录 (进行中)<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"陈恒捷\" title=\"陈恒捷(chenhengjie123)\" href=\"/chenhengjie123\">陈恒捷<\/a>\n for <a class=\"team-name\" data-name=\"PPmoney\" title=\"PPmoney(ppmoney)\" href=\"/ppmoney\">PPmoney<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18171#reply-141020\">46<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-18064\">\n <div class=\"avatar media-left\">\n <a title=\"debugtalk (debugtalk)\" href=\"/debugtalk\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/6109.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"我的 2018 年终总结\" href=\"/topics/18064\">我的 2018 年终总结<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"debugtalk\" title=\"debugtalk(debugtalk)\" href=\"/debugtalk\">debugtalk<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/18064#reply-138728\">28<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17986\">\n <div class=\"avatar media-left\">\n <a title=\"t880216t (81\u20141)\" href=\"/t880216t\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/6859.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"基于 Jmeter 的 web 端接口自动化测试平台\" href=\"/topics/17986\">基于 Jmeter 的 web 端接口自动化测试平台<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"81\u20141\" title=\"81\u20141(t880216t)\" href=\"/t880216t\">81\u20141<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17986#reply-141340\">75<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17746\">\n <div class=\"avatar media-left\">\n <a title=\"weijb (jb)\" href=\"/weijb\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/13676/f12b84.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"听说安卓微信 7.0 不能抓 https?\" href=\"/articles/17746\">听说安卓微信 7.0 不能抓 https?<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"jb\" title=\"jb(weijb)\" href=\"/weijb\">jb<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17746#reply-141478\">45<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17554\">\n <div class=\"avatar media-left\">\n <a title=\"devin (Devin)\" href=\"/devin\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/3791/69f795.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"(开源) XMind2TestCase:一个高效测试用例设计的解决方案! \" href=\"/topics/17554\">(开源) XMind2TestCase:一个高效测试用例设计的解决方案! <\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"Devin\" title=\"Devin(devin)\" href=\"/devin\">Devin<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17554#reply-141235\">78<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17355\">\n <div class=\"avatar media-left\">\n <a title=\"youzancoder (有赞测试团队)\" href=\"/youzancoder\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/38409/c59d23.png!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"有赞全链路压测实战\" href=\"/articles/17355\">有赞全链路压测实战<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"有赞测试团队\" title=\"有赞测试团队(youzancoder)\" href=\"/youzancoder\">有赞测试团队<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17355#reply-141550\">14<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17251\">\n <div class=\"avatar media-left\">\n <a title=\"cay (蒋刚毅)\" href=\"/cay\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/8985/1522fe.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"[持续交付实践] Jenkins Pipeline 高可用设计方法\" href=\"/topics/17251\">[持续交付实践] Jenkins Pipeline 高可用设计方法<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"蒋刚毅\" title=\"蒋刚毅(cay)\" href=\"/cay\">蒋刚毅<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17251#reply-129650\">7<\/a>\n <\/div>\n<\/div>\n<div class=\"topic media topic-17179\">\n <div class=\"avatar media-left\">\n <a title=\"appetizer.io (AppetizerIO)\" href=\"/appetizer.io\"><img class=\"media-object avatar-48\" src=\"/uploads/user/avatar/11797.jpg!md\" /><\/a>\n <\/div>\n <div class=\"infos media-body\">\n <div class=\"title media-heading\">\n <a title=\"WiFi 无线拉起执行 APP 稳定性 / 压力测试,免 USB 连线\" href=\"/topics/17179\">WiFi 无线拉起执行 APP 稳定性 / 压力测试,免 USB 连线<\/a>\n <i title=\"精华帖\" class=\"fa fa-diamond\" data-toggle=\"tooltip\"><\/i>\n \n <\/div>\n <div class=\"info\">\n <a class=\"user-name\" data-name=\"AppetizerIO\" title=\"AppetizerIO(appetizer.io)\" href=\"/appetizer.io\">AppetizerIO<\/a>\n for <a class=\"team-name\" data-name=\"AppetizerIO\" title=\"AppetizerIO(appetizerio)\" href=\"/appetizerio\">AppetizerIO<\/a>\n <\/div>\n <\/div>\n <div class=\"count media-right\">\n <a class=\"state-false\" href=\"/topics/17179#reply-139462\">19<\/a>\n <\/div>\n<\/div>\n\n <\/div>\n <\/div>\n<\/div>\n\n\n <div class=\"index-locations panel panel-default\">\n <div class=\"panel-heading\">热门节点 <i class=\"fa fa-dot-circle-o\" aria-hidden=\"true\"><\/i><\/div>\n <div class=\"panel-body location-list\" style=\"text-align:center;\">\n <span class=\"name\"><a title=\"Appium\" data-id=\"23\" href=\"/topics/node23\">Appium<\/a><\/span>\n <span class=\"name\"><a title=\"招聘\" data-id=\"19\" href=\"/topics/node19\">招聘<\/a><\/span>\n <span class=\"name\"><a title=\"新手区\" data-id=\"36\" href=\"/topics/node36\">新手区<\/a><\/span>\n <span class=\"name\"><a title=\"接口测试\" data-id=\"62\" href=\"/topics/node62\">接口测试<\/a><\/span>\n <span class=\"name\"><a title=\"Bug 曝光台\" data-id=\"47\" href=\"/topics/node47\">Bug 曝光台<\/a><\/span>\n <span class=\"name\"><a title=\"违规处理区\" data-id=\"55\" href=\"/topics/node55\">违规处理区<\/a><\/span>\n <span class=\"name\"><a title=\"通用技术\" data-id=\"25\" href=\"/topics/node25\">通用技术<\/a><\/span>\n <span class=\"name\"><a title=\"移动测试基础\" data-id=\"33\" href=\"/topics/node33\">移动测试基础<\/a><\/span>\n <span class=\"name\"><a title=\"灌水\" data-id=\"11\" href=\"/topics/node11\">灌水<\/a><\/span>\n <span class=\"name\"><a title=\"自动化工具\" data-id=\"2\" href=\"/topics/node2\">自动化工具<\/a><\/span>\n <span class=\"name\"><a title=\"匿名吐槽\" data-id=\"37\" href=\"/topics/node37\">匿名吐槽<\/a><\/span>\n <span class=\"name\"><a title=\"Macaca\" data-id=\"68\" href=\"/topics/node68\">Macaca<\/a><\/span>\n <span class=\"name\"><a title=\"性能测试工具\" data-id=\"3\" href=\"/topics/node3\">性能测试工具<\/a><\/span>\n <span class=\"name\"><a title=\"Selenium\" data-id=\"73\" href=\"/topics/node73\">Selenium<\/a><\/span>\n <span class=\"name\"><a title=\"其他测试框架\" data-id=\"31\" href=\"/topics/node31\">其他测试框架<\/a><\/span>\n <span class=\"name\"><a title=\"问答\" data-id=\"20\" href=\"/topics/node20\">问答<\/a><\/span>\n <span class=\"name\"><a title=\"职业经验\" data-id=\"12\" href=\"/topics/node12\">职业经验<\/a><\/span>\n <span class=\"name\"><a title=\"活动沙龙\" data-id=\"24\" href=\"/topics/node24\">活动沙龙<\/a><\/span>\n <span class=\"name\"><a title=\"霍格沃兹测试学院\" data-id=\"81\" href=\"/topics/node81\">霍格沃兹测试学院<\/a><\/span>\n <span class=\"name\"><a title=\"移动性能测试\" data-id=\"32\" href=\"/topics/node32\">移动性能测试<\/a><\/span>\n <span class=\"name\"><a title=\"持续集成\" data-id=\"46\" href=\"/topics/node46\">持续集成<\/a><\/span>\n <span class=\"name\"><a title=\"Robotium\" data-id=\"39\" href=\"/topics/node39\">Robotium<\/a><\/span>\n <span class=\"name\"><a title=\"iOS 测试\" data-id=\"51\" href=\"/topics/node51\">iOS 测试<\/a><\/span>\n <span class=\"name\"><a title=\"UiAutomator\" data-id=\"53\" href=\"/topics/node53\">UiAutomator<\/a><\/span>\n <span class=\"name\"><a title=\"AI测试\" data-id=\"134\" href=\"/topics/node134\">AI测试<\/a><\/span>\n <span class=\"name\"><a title=\"测试管理\" data-id=\"44\" href=\"/topics/node44\">测试管理<\/a><\/span>\n <span class=\"name\"><a title=\"STF\" data-id=\"137\" href=\"/topics/node137\">STF<\/a><\/span>\n <span class=\"name\"><a title=\"社区管理\" data-id=\"13\" href=\"/topics/node13\">社区管理<\/a><\/span>\n <span class=\"name\"><a title=\"WeTest腾讯质量开发平台\" data-id=\"80\" href=\"/topics/node80\">WeTest腾讯质量开发平台<\/a><\/span>\n <span class=\"name\"><a title=\"树洞\" data-id=\"132\" href=\"/topics/node132\">树洞<\/a><\/span>\n <span class=\"name\"><a title=\"前端测试\" data-id=\"16\" href=\"/topics/node16\">前端测试<\/a><\/span>\n <span class=\"name\"><a title=\"fir.im\" data-id=\"49\" href=\"/topics/node49\">fir.im<\/a><\/span>\n <span class=\"name\"><a title=\"ATX\" data-id=\"78\" href=\"/topics/node78\">ATX<\/a><\/span>\n <span class=\"name\"><a title=\"Docker\" data-id=\"48\" href=\"/topics/node48\">Docker<\/a><\/span>\n <span class=\"name\"><a title=\"Linux\" data-id=\"65\" href=\"/topics/node65\">Linux<\/a><\/span>\n <\/div>\n <\/div>\n\n\n <div class=\"index-locations panel panel-default\">\n <div class=\"panel-heading\">Popular locations <i class=\"fa fa-map-marker\"><\/i> <\/div>\n <div class=\"panel-body location-list\" style=\"text-align:center;\">\n <span class=\"name\"><a href=\"/users/city/%E5%8C%97%E4%BA%AC\">北京<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E4%B8%8A%E6%B5%B7\">上海<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E6%B7%B1%E5%9C%B3\">深圳<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E6%9D%AD%E5%B7%9E\">杭州<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E5%B9%BF%E5%B7%9E\">广州<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E6%88%90%E9%83%BD\">成都<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E5%8D%97%E4%BA%AC\">南京<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E8%A5%BF%E5%AE%89\">西安<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E6%AD%A6%E6%B1%89\">武汉<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E8%8B%8F%E5%B7%9E\">苏州<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E5%8E%A6%E9%97%A8\">厦门<\/a><\/span>\n <span class=\"name\"><a href=\"/users/city/%E9%87%8D%E5%BA%86\">重庆<\/a><\/span>\n <\/div>\n <\/div>\n\n \r\n\r\n<div class=\"row home-icons hidden-sm hidden-xs\">\r\n <div class=\"col-md-3\">\r\n <div class=\"item item5\">\r\n <div class=\"icon\">\r\n <img src=\"https://testerhome.com/weixin.jpg\" width=\"100%\" height=\"100%\">\r\n <\/div>\r\n <div class=\"text\">\r\n <a href=\"/topics/popular\">公众号<i class=\"pull-right fa fa-arrow-right\"><\/i><\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n <div class=\"col-md-3\">\r\n <div class=\"item item6\">\r\n <div class=\"icon\">\r\n <img src=\"/photo/2016/45740b8b1487cc191b585bc2da978f06.jpeg\" width=\"100%\" height=\"100%\">\r\n <\/div>\r\n <div class=\"text\">\r\n <a href=\"/topics/popular\">个人号助手<i class=\"pull-right fa fa-arrow-right\"><\/i><\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\r\n <div class=\"col-md-3\">\r\n <div class=\"item item6\">\r\n <div class=\"icon\">\r\n <img src=\"https://testerhome.com/uploads/photo/2017/94db8410-69e5-4cfa-b4c7-66d8783f2bc5.png!large\" width=\"100%\" height=\"100%\">\r\n <\/div>\r\n <div class=\"text\">\r\n <a href=\"https://fir.im/w2j5\">Android 客户端<i class=\"pull-right fa fa-arrow-right\"><\/i><\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\r\n <div class=\"col-md-3\">\r\n <div class=\"item item6\">\r\n <div class=\"icon\">\r\n <img src=\"https://testerhome.com/uploads/photo/2016/bcff0e5a77254c32d9f35760dbc9b369.png!large\" width=\"100%\" height=\"100%\">\r\n <\/div>\r\n <div class=\"text\">\r\n <a href=\"https://itunes.apple.com/us/app/testerhome-guan-fang-ke-hu/id1182812600?ls=1&mt=8\">iOS 客户端<i class=\"pull-right fa fa-arrow-right\"><\/i><\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n\r\n\r\n\r\n<\/div>\n <\/div>\n <div class=\"col-md-3 home-side-bar\">\n \n\n <div class=\"home_suggest_topics panel panel-default\">\n <div class=\"panel-heading\">七日最热 Top10<\/div>\n <div class=\"panel-body\">\n <div class=\"sidebar_topic topic-18785\">\n <div class=\"title\">\n <span class=\"label label-default top3\">1<\/span><\/a>\n\n <a title=\"一道有魔性的面试题 (来自今日头条)\" href=\"/topics/18785\">\n 一道有魔性的面试题 (来自今日头条)\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18775\">\n <div class=\"title\">\n <span class=\"label label-default top3\">2<\/span><\/a>\n\n <a title=\"社会萌新 想问一下不能长期做手工测试的原因\" href=\"/topics/18775\">\n 社会萌新 想问一下不能长期做手工测试的原因\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18813\">\n <div class=\"title\">\n <span class=\"label label-default top3\">3<\/span><\/a>\n\n <a title=\"请问大家自动化时数据依赖的解决方式?感谢\" href=\"/topics/18813\">\n 请问大家自动化时数据依赖的解决方式?感谢\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18798\">\n <div class=\"title\">\n <span class=\"label label-default\">4<\/span><\/a>\n\n <a title=\"乐信集团 (原分期乐) 招人啦,各位大大们快到碗里来\" href=\"/topics/18798\">\n 乐信集团 (原分期乐) 招人啦,各位大大们快到碗里来\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18824\">\n <div class=\"title\">\n <span class=\"label label-default\">5<\/span><\/a>\n\n <a title=\"请教一个 Jenkins 发送邮件带附件的问题\" href=\"/topics/18824\">\n 请教一个 Jenkins 发送邮件带附件的问题\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18777\">\n <div class=\"title\">\n <span class=\"label label-default\">6<\/span><\/a>\n\n <a title=\"有一个疑惑想问大家\" href=\"/topics/18777\">\n 有一个疑惑想问大家\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18750\">\n <div class=\"title\">\n <span class=\"label label-default\">7<\/span><\/a>\n\n <a title=\"修改网页 JS 解除 某资格网 登录 60s 限制问题\" href=\"/topics/18750\">\n 修改网页 JS 解除 某资格网 登录 60s 限制问题\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18789\">\n <div class=\"title\">\n <span class=\"label label-default\">8<\/span><\/a>\n\n <a title=\"[求助] Battery Historian 工具 网页加载报错 无法上传日志文件 推测是 historian-optimized.js 缺失\" href=\"/topics/18789\">\n [求助] Battery Historian 工具 网页加载报错 无法上传日志文件 推测是 historian-optimized.js 缺失\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18770\">\n <div class=\"title\">\n <span class=\"label label-default\">9<\/span><\/a>\n\n <a title=\"这几天很火的套路招聘图片\u2026\u2026\" href=\"/topics/18770\">\n 这几天很火的套路招聘图片\u2026\u2026\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18752\">\n <div class=\"title\">\n <span class=\"label label-default\">10<\/span><\/a>\n\n <a title=\"记一次基于 Robotium 改造的测试实践\" href=\"/topics/18752\">\n 记一次基于 Robotium 改造的测试实践\n<\/a> <\/div>\n<\/div>\n\n <\/div>\n <\/div>\n\n <div class=\"home_suggest_topics panel panel-default\">\n <div class=\"panel-heading\">最新 bug <i class=\"fa fa-bug\" aria-hidden=\"true\"><\/i><\/div>\n <div class=\"panel-body\">\n <div class=\"sidebar_topic topic-18838\">\n <div class=\"title\">\n <span class=\"label label-default top3\">1<\/span><\/a>\n\n <a title=\"使用微信共享 iphone 的语音备忘录,出现 \u201c发生异常,无法分享\u201d 的提示弹框。企业微信可以正常分享。\" href=\"/topics/18838\">\n 使用微信共享 iphone 的语音备忘录,出现 \u201c发生异常,无法分享\u201d 的提示弹框。企业微信可以正常分享。\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18827\">\n <div class=\"title\">\n <span class=\"label label-default top3\">2<\/span><\/a>\n\n <a title=\"马蜂窝的一个小 bug\" href=\"/topics/18827\">\n 马蜂窝的一个小 bug\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18803\">\n <div class=\"title\">\n <span class=\"label label-default top3\">3<\/span><\/a>\n\n <a title=\"阿里旺旺的一个 bug\" href=\"/topics/18803\">\n 阿里旺旺的一个 bug\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18800\">\n <div class=\"title\">\n <span class=\"label label-default\">4<\/span><\/a>\n\n <a title=\"testerhome 手机端 app bug\" href=\"/topics/18800\">\n testerhome 手机端 app bug\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-18764\">\n <div class=\"title\">\n <span class=\"label label-default\">5<\/span><\/a>\n\n <a title=\"mongodb 数据库官网出现的 bug\" href=\"/topics/18764\">\n mongodb 数据库官网出现的 bug\n<\/a> <\/div>\n<\/div>\n\n <\/div>\n <\/div>\n\n<div class=\"home_suggest_topics panel panel-default\">\n<div class=\"panel-heading\">最新收录开源测试项目<\/div>\n<div class=\"panel-body\">\n <div class=\"sidebar_topic topic-72\">\n <div class=\"title\">\n <img class=\"avatar-small\" src=\"/uploads/opensource_project/2019/2ee30f36-7a18-4799-95f9-284a8a5b448c.png\">\n <a title=\"基于图像识别的UI自动化解决方案-FITCH\" href=\"/opensource_projects/https---github-com-williamfzc-fitch\">\n 基于图像识别的UI自动化解决方案-FITCH\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-67\">\n <div class=\"title\">\n <img class=\"avatar-small\" src=\"/uploads/opensource_project/2019/47ca5738-f46e-4ed1-9888-b8a3d23d99be.png\">\n <a title=\"《 iOS-checkIPA 》ipa文件信息检查工具\" href=\"/opensource_projects/ios-checkipa\">\n 《 iOS-checkIPA 》ipa文件信息检查工具\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-73\">\n <div class=\"title\">\n <img class=\"avatar-small\" src=\"/uploads/opensource_project/2019/6e016e67-df4c-497d-9466-e2c2c65fc091.png\">\n <a title=\"Android APP 小工具测试\u201c利器\u201d\" href=\"/opensource_projects/android-app-testtools\">\n Android APP 小工具测试\u201c利器\u201d\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-71\">\n <div class=\"title\">\n <img class=\"avatar-small\" src=\"/uploads/opensource_project/2019/f329de76-35f3-471c-b598-b386c8ad6109.png\">\n <a title=\"RDebug\" href=\"/opensource_projects/rdebug\">\n RDebug\n<\/a> <\/div>\n<\/div>\n<div class=\"sidebar_topic topic-70\">\n <div class=\"title\">\n <img class=\"avatar-small\" src=\"/uploads/opensource_project/2019/37f745ce-7406-49ce-bb79-30eea20233d4.png\">\n <a title=\"HAF\" href=\"/opensource_projects/haf\">\n HAF\n<\/a> <\/div>\n<\/div>\n\n<\/div>\n<\/div>\n\n\n<div id=\"hall-of-fames\" class=\"panel panel-default\">\n <div class=\"panel-heading\">最新加入 <i class='fa fa-fire'><\/i>(新同学)<\/div>\n <div class=\"panel-body\">\n <div class=\"users-label\">\n <a class=\"users-label-item\" href=\"/hulcwater\" title=\"\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42774/915ed5.jpg!sm\">\n hulcwater\n <\/a>\n <a class=\"users-label-item\" href=\"/Simonluepang\" title=\"Shenwei Xu\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42771/634f68.jpg!sm\">\n Simonluepang\n <\/a>\n <a class=\"users-label-item\" href=\"/iamlonghan\" title=\"longhan\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42762/0f1bcc.jpg!sm\">\n iamlonghan\n <\/a>\n <a class=\"users-label-item\" href=\"/HondaXX\" title=\"\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42760/58cb18.jpg!sm\">\n HondaXX\n <\/a>\n <a class=\"users-label-item\" href=\"/ttsiii\" title=\"天都\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42759/bfd30a.jpg!sm\">\n ttsiii\n <\/a>\n <a class=\"users-label-item\" href=\"/DDDDanny\" title=\"TDanny\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42756/ef97fc.jpg!sm\">\n DDDDanny\n <\/a>\n <a class=\"users-label-item\" href=\"/yhx0326\" title=\"yhx\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42754/8c6f19.png!sm\">\n yhx0326\n <\/a>\n <a class=\"users-label-item\" href=\"/zhl12345678\" title=\"\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42750/f8b97a.jpg!sm\">\n zhl12345678\n <\/a>\n <a class=\"users-label-item\" href=\"/shuaimingming\" title=\"帅洺洺\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42746/53cbf6.jpg!sm\">\n shuaimingming\n <\/a>\n <a class=\"users-label-item\" href=\"/todayno\" title=\"今天不打烊\">\n <img class=\"avatar-small inline-block\" src=\"/uploads/user/avatar/42743/04e73c.jpg!sm\">\n todayno\n <\/a>\n <\/div>\n\n <\/div>\n<\/div>\n\n\n\n <div class=\"panel panel-default\">\n <div class=\"panel-heading\">Statistics<\/div>\n <ul class=\"list-group\">\n <li class=\"list-group-item\">社区会员: 37899 人<\/li>\n <li class=\"list-group-item\">帖子数: 18702 个<\/li>\n <li class=\"list-group-item\">回帖数: 141051 条<\/li>\n <\/ul>\n <\/div>\n\n <\/div>\n<\/div>\n<\/div>\n\n <footer class=\"footer\" id=\"footer\" data-turbolinks-permanent>\n <div class=\"container\">\n \r\n\r\n<div class=\"row\">\r\n <div class=\"col-sm-9\">\r\n <div class=\"media\">\r\n <div class=\"media-left\">\r\n <img src=\"https://testerhome.com/uploads/photo/2016/274e7ebc8ad0db0b7e718fceea3628a9.png!large\" style=\"width:48px;\">\r\n <\/div>\r\n <div class=\"media-body\">\r\n\r\n <div class=\"links\">\r\n <a href=\"https://testerhome.com/wiki/about\">关于<\/a> / \r\n <a href=\"https://testerhome.com/users\">活跃用户<\/a> / \r\n <a href=\"http://test-china.org/\" target=\"_blank\">中国移动互联网测试技术大会<\/a> / \r\n <a href=\"/topics/node13\">反馈<\/a> / \r\n <a href=\"https://github.com/testerhome\">Github<\/a> / \r\n <a href=\"https://testerhome.com//api-doc/\">API<\/a> / \r\n <a href=\"/wiki/spreadtesterhome\">帮助推广<\/a>\r\n\r\n <\/div>\r\n <div class=\"copyright\" style=\"font-size:14px; color:#9CA4A9;margin-top:0px;margin-bottom:5px\">\r\n TesterHome 移动测试社区,由众多移动测试工作者维护,致力于推进国内测试技术。Inspired by RubyChina\r\n <\/div>\r\n<div class=\"links\" data-no-turbolink=\"\">\r\n<span style=\"font-size:14px; color:#666;\">友情链接<\/span><span style=\"margin-left:5px\">\r\n <a style=\"color:#317DDA;\" href=\"http://wetest.qq.com/?from=links_testerhome\" target=\"_blank\">WeTest腾讯质量开放平台<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://www.infoq.com/cn\" target=\"_blank\">InfoQ<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://www.testtao.com/portal.php\" target=\"_blank\">测试之道<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"https://www.testwo.com/\" target=\"_blank\">测试窝<\/a> / \r\n <a style=\"color:#317DDA;\" href=\"http://tieba.baidu.com/f?ie=utf-8&kw=%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95&fr=search\" target=\"_blank\">百度测试吧<\/a> /\r\n <a style=\"color:#317DDA;\" href=\"http://www.itdks.com/\" target=\"_blank\">IT大咖说<\/a>\r\n<\/span>\r\n <\/div>\r\n <div class=\"links\" style=\"margin-top:0px\" data-no-turbolink=\"\">\r\n <a href=\"?locale=zh-CN\" rel=\"nofollow\">简体中文<\/a> / <a href=\"?locale=zh-TW\" rel=\"nofollow\">正體中文<\/a> / <a href=\"?locale=en\"\r\n rel=\"nofollow\">English<\/a>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n <\/div>\r\n <div class=\"col-sm-3 friends\">\r\n <a href=\"http://www.ucloud.cn/?utm_source=zanzhu&utm_campaign=testerhome&utm_medium=display&utm_content=yejiao&ytag=testerhome_logo\"\r\n target=\"_blank\" rel=\"twipsy\" style=\"display:inline-block;margin-right:5px;\" data-original-title=\"本站服务器由 Ucloud 赞助\"><img src=\"https://testerhome.com/photo/2016/4ce97c93f9433f654884c4839408327a.png\" style=\"height:28px\"><\/a>\r\n\r\n <a href=\"http://www.sendcloud.net/\"\r\n target=\"_blank\" rel=\"twipsy\" style=\"display:inline-block;margin-right:5px;\" data-original-title=\"邮件服务由 SendCloud 赞助\"><img src=\"https://testerhome.com/uploads/photo/2017/0f9fb5db-2472-4430-9f7e-6c051f28c3c9.png\" style=\"height:28px\"><\/a>\r\n\r\n <\/div>\r\n<\/div>\r\n\r\n\r\n\n <\/div>\n <\/footer>\n\n<script type=\"text/javascript\" data-turbolinks-eval=\"false\">\n App.root_url = \"https://testerhome.com/\";\n App.asset_url = \"\";\n App.twemoji_url = \"https://twemoji.b0.upaiyun.com/2\";\n App.locale = \"en\";\n App.current_user_id = 6109;\n<\/script>\n\n<script>\n ga('create', 'UA-45014075-1', 'auto');\n ga('require', 'displayfeatures');\n ga('send', 'pageview');\n<\/script>\n<div class=\"zoom-overlay\"><\/div>\n<\/div>\n<\/body>\n<\/html>\n"},"redirectURL":null,"headersSize":0,"bodySize":11373},"serverIPAddress":"106.75.214.88","cache":{},"timings":{"dns":-1,"connect":-1,"ssl":-1,"send":1,"wait":152,"receive":5}}]}} \ No newline at end of file diff --git a/data/testerhome-login.yml b/data/testerhome-login.yml new file mode 100644 index 00000000..e24dccf8 --- /dev/null +++ b/data/testerhome-login.yml @@ -0,0 +1,66 @@ +- config: + name: testcase description + variables: {} + verify: False + +- test: + name: /account/sign_in + request: + headers: + If-None-Match: W/"bc9ae267fdcbd89bf1dfaea10dea2b0e" + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 + (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 + method: GET + url: https://testerhome.com/account/sign_in + extract: + X_CSRF_Token: <meta name="csrf-token" content="(.*)" /> + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, text/html; charset=utf-8] + +- test: + name: /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png + request: + headers: + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 + (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 + method: GET + url: https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, image/png] + +- test: + name: /account/sign_in + request: + data: + commit: Sign In + user[login]: debugtalk + user[password]: XXXXXXXX + user[remember_me]: '1' + utf8: ✓ + headers: + Content-Type: application/x-www-form-urlencoded; charset=UTF-8 + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 + (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 + X-CSRF-Token: $X_CSRF_Token + X-Requested-With: XMLHttpRequest + method: POST + url: https://testerhome.com/account/sign_in + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, text/javascript; charset=utf-8] + +- test: + name: / + request: + headers: + If-None-Match: W/"bad62c68dac27b01151516aad5c7f0be" + Turbolinks-Referrer: https://testerhome.com/account/sign_in + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 + (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 + method: GET + url: https://testerhome.com/ + validate: + - eq: [status_code, 200] + - eq: [headers.Content-Type, text/html; charset=utf-8] diff --git a/data/user_id.csv b/data/user_id.csv new file mode 100644 index 00000000..ae3d9c5f --- /dev/null +++ b/data/user_id.csv @@ -0,0 +1,5 @@ +user_id +1001 +1002 +1003 +1004 diff --git a/development/architecture/index.html b/development/architecture/index.html new file mode 100644 index 00000000..28099b96 --- /dev/null +++ b/development/architecture/index.html @@ -0,0 +1,744 @@ + + + + +<!doctype html> +<html lang="zh" class="no-js"> + <head> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta http-equiv="x-ua-compatible" content="ie=edge"> + + <meta name="description" content="HttpRunner V2.x User Documentation"> + + + + <meta name="author" content="debugtalk"> + + + <meta name="lang:clipboard.copy" content="复制"> + + <meta name="lang:clipboard.copied" content="已复制"> + + <meta name="lang:search.language" content="jp"> + + <meta name="lang:search.pipeline.stopwords" content="True"> + + <meta name="lang:search.pipeline.trimmer" content="True"> + + <meta name="lang:search.result.none" content="没有找到符合条件的结果"> + + <meta name="lang:search.result.one" content="找到 1 个符合条件的结果"> + + <meta name="lang:search.result.other" content="# 个符合条件的结果"> + + <meta name="lang:search.tokenizer" content="[\uff0c\u3002]+"> + + <link rel="shortcut icon" href="../../assets/images/favicon.png"> + <meta name="generator" content="mkdocs-1.0.4, mkdocs-material-4.4.3"> + + + + <title>Pipline - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + +
+
+ + + +

Pipline

+ +

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/development/dev-api/index.html b/development/dev-api/index.html new file mode 100644 index 00000000..69e421f8 --- /dev/null +++ b/development/dev-api/index.html @@ -0,0 +1,1268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 基础库调用 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + + + +
+
+ + + +

开发扩展

+

HttpRunner 除了作为命令行工具使用外,还可以作为软件包集成到你自己的项目中。

+

简单来说,HttpRunner 提供了运行 YAML/JSON 格式测试用例的能力,并能返回详细的测试结果信息。

+

HttpRunner class

+

HttpRunner 以 类(class) 的形式对外提供调用支持,类名为HttpRunner,导入方式如下:

+
from httprunner.api import HttpRunner
+
+ + +

可用初始化参数

+

HttpRunner 内部用于驱动测试执行的是unittest.TextTestRunner,在初始化 HttpRunner 时可以使用 TextTestRunner 的所有可用参数(详情可阅读官方文档)。除此之外,HttpRunner还额外支持一个参数,http_client_session,可用于指定不同的客户端类型。

+

通常情况下,初始化 HttpRunner 时常用的参数有如下几个:

+
    +
  • resultclass: HtmlTestResult/TextTestResult,默认值为 HtmlTestResult
  • +
  • failfast: 设置为 True 时,测试在首次遇到错误或失败时会停止运行;默认值为 False
  • +
  • http_client_session: 传入requests.Session()时进行自动化测试(默认),传入locust.client.Session()时进行性能测试
  • +
+

例如,如需初始化 HttpRunner 时设置 failfast 为 False,初始化方式如下所示:

+
from httprunner.api import HttpRunner
+
+runner = HttpRunner(failfast=False)
+
+ + +

可用调用方法

+

HttpRunner 中,对外提供了两个方法:

+
    +
  • run: 运行测试用例
  • +
  • gen_html_report: 生成 HTML 测试报告
  • +
+

可用属性

+

HttpRunner 中,对外提供了一个属性:

+
    +
  • summary: 测试执行结果
  • +
+

该属性需要在调用 run 方法后获取。

+

运行测试用例

+

HttpRunner 的 run 方法有三个参数:

+
    +
  • path_or_testcases: 指定要运行的测试用例;支持传入两类参数,YAML/JSON 格式测试用例文件路径,或者标准的测试用例结构体;
  • +
  • dot_env_path(可选): 指定加载环境变量文件(.env)的路径,默认值为当前工作目录(PWD)中的 .env 文件
  • +
  • mapping(可选): 变量映射,可用于对传入测试用例中的变量进行覆盖替换。
  • +
+

传入测试用例文件路径

+

指定测试用例文件路径支持三种形式:

+
    +
  • YAML/JSON 文件路径,支持绝对路径和相对路径
  • +
  • 包含 YAML/JSON 文件的文件夹,支持绝对路径和相对路径
  • +
  • 文件路径和文件夹路径的混合情况(list/set)
  • +
+
# 文件路径
+runner.run("docs/data/demo-quickstart-2.yml")
+
+# 文件夹路径
+runner.run("docs/data/")
+
+# 混合情况
+runner.run(["docs/data/", "files/demo-quickstart-2.yml"])
+
+ + +

如需指定加载环境变量文件(.env)的路径,或者需要对测试用例中的变量进行覆盖替换,则可使用 dot_env_pathmapping 参数。

+
# dot_env_path
+runner.run("docs/data/demo-quickstart-2.yml", dot_env_path="/path/to/.env")
+
+# mapping
+override_mapping = {
+    "device_sn": "XXX"
+}
+runner.run("docs/data/demo-quickstart-2.yml", mapping=override_mapping)
+
+ + +

传入标准的测试用例结构体

+

除了传入测试用例文件路径,还可以直接传入标准的测试用例结构体。

+

demo-quickstart-2.yml 为例,对应的数据结构体如下所示:

+
[
+  {
+    "config": {
+      "name": "testcase description",
+      "request": {
+        "base_url": "",
+        "headers": {
+          "User-Agent": "python-requests/2.18.4"
+        }
+      },
+      "variables": [],
+      "output": ["token"],
+      "path": "/abs-path/to/demo-quickstart-2.yml",
+      "refs": {
+        "env": {},
+        "debugtalk": {
+          "variables": {
+            "SECRET_KEY": "DebugTalk"
+          },
+          "functions": {
+            "gen_random_string": <function gen_random_string at 0x108596268>,
+            "get_sign": <function get_sign at 0x1085962f0>,
+            "get_user_id": <function get_user_id at 0x108596378>,
+            "get_account": <function get_account at 0x108596400>,
+            "get_os_platform": <function get_os_platform at 0x108596488>
+          }
+        },
+        "def-api": {},
+        "def-testcase": {}
+      }
+    },
+    "teststeps": [
+      {
+        "name": "/api/get-token",
+        "request": {
+          "url": "http://127.0.0.1:5000/api/get-token",
+          "method": "POST",
+          "headers": {"Content-Type": "application/json", "app_version": "2.8.6", "device_sn": "FwgRiO7CNA50DSU", "os_platform": "ios", "user_agent": "iOS/10.3"},
+          "json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"}
+        },
+        "extract": [
+          {"token": "content.token"}
+        ],
+        "validate": [
+          {"eq": ["status_code", 200]},
+          {"eq": ["headers.Content-Type", "application/json"]},
+          {"eq": ["content.success", true]}
+        ]
+      },
+      {
+        "name": "/api/users/1000",
+        "request": {"url": "http://127.0.0.1:5000/api/users/1000", "method": "POST", "headers": {"Content-Type": "application/json", "device_sn": "FwgRiO7CNA50DSU", "token": "$token"},
+        "json": {"name": "user1", "password": "123456"}},
+        "validate": [
+          {"eq": ["status_code", 201]},
+          {"eq": ["headers.Content-Type", "application/json"]},
+          {"eq": ["content.success", true]},
+          {"eq": ["content.msg", "user created successfully."]}
+        ]
+      }
+    ]
+  },
+  {...} # another testcase
+]
+
+ + +

传入测试用例结构体时,支持传入单个结构体(dict),或者多个结构体(list of dict)。

+
# 运行单个结构体
+runner.run(testcase)
+
+# 运行多个结构体
+runner.run([testcase1, testcase2])
+
+ + +

加载 debugtalk.py && .env

+

通过传入测试用例文件路径运行测试用例时,HttpRunner 会自动以指定测试用例文件路径为起点,向上搜索 debugtalk.py 文件,并将 debugtalk.py 文件所在的文件目录作为当前工作目录(PWD)。

+

同时,HttpRunner 会在当前工作目录(PWD)下搜索 .env 文件,以及 apitestcases 文件夹,并自动进行加载。

+

最终加载得到的存储结构如下所示:

+
{
+  "env": {},
+  "debugtalk": {
+    "variables": {
+      "SECRET_KEY": "DebugTalk"
+    },
+    "functions": {
+      "gen_random_string": <function gen_random_string at 0x108596268>,
+      "get_sign": <function get_sign at 0x1085962f0>,
+      "get_user_id": <function get_user_id at 0x108596378>,
+      "get_account": <function get_account at 0x108596400>,
+      "get_os_platform": <function get_os_platform at 0x108596488>
+    }
+  },
+  "def-api": {},
+  "def-testcase": {}
+}
+
+ + +

其中,env 对应的是 .env 文件中的环境变量,debugtalk 对应的是 debugtalk.py 文件中定义的变量和函数,def-api 对应的是 api 文件夹下定义的接口描述,def-testcase 对应的是 testcases 文件夹下定义的测试用例。

+

通过传入标准的测试用例结构体执行测试时,传入的数据应包含所有信息,包括 debugtalk.py.env、依赖的 api 和 测试用例等;因此也无需再使用 dot_env_pathmapping 参数,所有信息都要通过 refs 传入。

+

返回详细测试结果数据

+

运行完成后,通过 summary 属性可获取详尽的运行结果数据。

+
# get result summary
+summary = runner.summary
+
+ + +

其数据结构为:

+
{
+  "success": true,
+  "stat": {
+    "testsRun": 2,
+    "failures": 0,
+    "errors": 0,
+    "skipped": 0,
+    "expectedFailures": 0,
+    "unexpectedSuccesses": 0,
+    "successes": 2
+  },
+  "time": {
+    "start_at": 1538449655.944404,
+    "duration": 0.03181314468383789
+  },
+  "platform": {
+    "httprunner_version": "1.5.14",
+    "python_version": "CPython 3.6.5+",
+    "platform": "Darwin-17.6.0-x86_64-i386-64bit"
+  },
+  "details": [
+    {
+      "success": true,
+      "name": "testcase description",
+      "base_url": "",
+      "stat": {"testsRun": 2, "failures": 0, "errors": 0, "skipped": 0, "expectedFailures": 0, "unexpectedSuccesses": 0, "successes": 2},
+      "time": {"start_at": 1538449655.944404, "duration": 0.03181314468383789},
+      "records": [
+        {
+          "name": "/api/get-token",
+          "status": "success",
+          "attachment": "",
+          "meta_data": {
+            "request": {
+              "url": "http://127.0.0.1:5000/api/get-token",
+              "method": "POST",
+              "headers": {"User-Agent": "python-requests/2.18.4", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "keep-alive", "Content-Type": "application/json", "app_version": "2.8.6", "device_sn": "FwgRiO7CNA50DSU", "os_platform": "ios", "user_agent": "iOS/10.3", "Content-Length": "52"},
+              "start_timestamp": 1538449655.944801,
+              "json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"},
+              "body": b'{"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"}'
+            },
+            "response": {
+              "status_code": 200,
+              "headers": {"Content-Type": "application/json", "Content-Length": "46", "Server": "Werkzeug/0.14.1 Python/3.6.5+", "Date": "Tue, 02 Oct 2018 03:07:35 GMT"},
+              "content_size": 46,
+              "response_time_ms": 12.87,
+              "elapsed_ms": 6.955,
+              "encoding": null,
+              "content": b'{"success": true, "token": "CcQ7dBjZZbjIXRkG"}',
+              "content_type": "application/json",
+              "ok": true,
+              "url": "http://127.0.0.1:5000/api/get-token",
+              "reason": "OK",
+              "cookies": {},
+              "text": '{"success": true, "token": "CcQ7dBjZZbjIXRkG"}',
+              "json": {"success": true, "token": "CcQ7dBjZZbjIXRkG"}
+            },
+            "validators": [
+              {"check": "status_code", "expect": 200, "comparator": "eq", "check_value": 200, "check_result": "pass"},
+              {"check": "headers.Content-Type", "expect": "application/json", "comparator": "eq", "check_value": "application/json", "check_result": "pass"},
+              {"check": "content.success", "expect": true, "comparator": "eq", "check_value": true, "check_result": "pass"}
+            ]
+          }
+        },
+        {
+          "name": "/api/users/1000",
+          "status": "success",
+          "attachment": "",
+          "meta_data": {
+            "request": {
+              "url": "http://127.0.0.1:5000/api/users/1000",
+              "method": "POST",
+              "headers": {"User-Agent": "python-requests/2.18.4", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "keep-alive", "Content-Type": "application/json", "device_sn": "FwgRiO7CNA50DSU", "token": "CcQ7dBjZZbjIXRkG", "Content-Length": "39"},
+              "start_timestamp": 1538449655.958944,
+              "json": {"name": "user1", "password": "123456"},
+              "body": b'{"name": "user1", "password": "123456"}'
+            },
+            "response": {
+              "status_code": 201,
+              "headers": {"Content-Type": "application/json", "Content-Length": "54", "Server": "Werkzeug/0.14.1 Python/3.6.5+", "Date": "Tue, 02 Oct 2018 03:07:35 GMT"},
+              "content_size": 54,
+              "response_time_ms": 3.34,
+              "elapsed_ms": 2.16,
+              "encoding": null,
+              "content": b'{"success": true, "msg": "user created successfully."}',
+              "content_type": "application/json",
+              "ok": true,
+              "url": "http://127.0.0.1:5000/api/users/1000",
+              "reason": "CREATED",
+              "cookies": {},
+              "text": '{"success": true, "msg": "user created successfully."}',
+              "json": {"success": true, "msg": "user created successfully."}
+            },
+            "validators": [
+              {"check": "status_code", "expect": 201, "comparator": "eq", "check_value": 201, "check_result": "pass"},
+              {"check": "headers.Content-Type", "expect": "application/json", "comparator": "eq", "check_value": "application/json", "check_result": "pass"},
+              {"check": "content.success", "expect": true, "comparator": "eq", "check_value": true, "check_result": "pass"},
+              {"check": "content.msg", "expect": "user created successfully.", "comparator": "eq", "check_value": "user created successfully.", "check_result": "pass"}
+            ]
+          }
+        }
+      ],
+      "in_out": {
+        "in": {"SECRET_KEY": "DebugTalk"},
+        "out": {"token": "CcQ7dBjZZbjIXRkG"}
+      }
+    }
+  ]
+}
+
+ + +

生成 HTML 测试报告

+

如需生成 HTML 测试报告,可调用 gen_html_report 方法。

+
# generate html report
+runner.gen_html_report(
+    html_report_name="demo",
+    html_report_template="/path/to/custom_report_template"
+)
+
+# => reports/demo/demo-1532078874.html
+
+ + +

关于测试报告的详细内容,请查看测试报告部分。

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/testerhome-login/index.html b/examples/testerhome-login/index.html new file mode 100644 index 00000000..ec5162f1 --- /dev/null +++ b/examples/testerhome-login/index.html @@ -0,0 +1,1124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TesterHome 登录 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

TesterHome 登录

+ +

案例介绍

+

通过接口自动化实现 TesterHome 的登录退出功能。

+

+

功能描述:

+
    +
  • 进入登录页面
  • +
  • 输入账号和密码
  • +
  • 点击【Sign In】进行登录
  • +
+

准备工作

+

抓包生成 HAR 文件

+

在浏览器中人工进行登录操作,同时使用抓包工具进行抓包。抓包时建议使用过滤器(Filter),常用的做法是采用被测系统的 host,将无关请求过滤掉。

+

+

选择需要生成测试用例的请求,导出为 HTTP Archive (.har) 格式的文件。

+

+

+

转换生成测试用例

+

成功安装 HttpRunner 后,系统中会新增 har2case 命令,使用该命令可将 HAR 数据包转换为 HttpRunner 支持的 YAML/JSON 测试用例文件。

+
$ har2case docs/data/testerhome-login.har -2y
+INFO:root:Start to generate testcase.
+INFO:root:dump testcase to YAML format.
+INFO:root:Generate YAML testcase successfully: docs/data/testerhome-login.yml
+
+ + +

生成的测试用例内容如下:

+
+点击查看 + +
- config:
+    name: testcase description
+    variables: {}
+
+- test:
+    name: /account/sign_in
+    request:
+        headers:
+            If-None-Match: W/"bc9ae267fdcbd89bf1dfaea10dea2b0e"
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+        method: GET
+        url: https://testerhome.com/account/sign_in
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/html; charset=utf-8]
+
+- test:
+    name: /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png
+    request:
+        headers:
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+        method: GET
+        url: https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, image/png]
+
+- test:
+    name: /account/sign_in
+    request:
+        data:
+            commit: Sign In
+            user[login]: debugtalk
+            user[password]: XXXXXXXX
+            user[remember_me]: '1'
+            utf8: 
+        headers:
+            Content-Type: application/x-www-form-urlencoded; charset=UTF-8
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+            X-CSRF-Token: 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA==
+            X-Requested-With: XMLHttpRequest
+        method: POST
+        url: https://testerhome.com/account/sign_in
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/javascript; charset=utf-8]
+
+- test:
+    name: /
+    request:
+        headers:
+            If-None-Match: W/"bad62c68dac27b01151516aad5c7f0be"
+            Turbolinks-Referrer: https://testerhome.com/account/sign_in
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+        method: GET
+        url: https://testerhome.com/
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/html; charset=utf-8]
+
+ + +
+ +

首次运行测试用例

+

成功安装 HttpRunner 后,系统中会新增 hrun 命令,该命令是 HttpRunner 的核心命令,用于运行 HttpRunner 支持的 YAML/JSON 测试用例文件。

+

生成测试用例后,我们可以先尝试运行一次,大多数情况,如果被测场景中不存在关联的情况,是可以直接运行成功的。

+
$ hrun docs/data/testerhome-login.yml --failfast --log-level info
+INFO     Start to run testcase: testcase description
+/account/sign_in
+INFO     GET https://testerhome.com/account/sign_in
+INFO     status_code: 200, response_time(ms): 189.66 ms, response_length: 12584 bytes
+
+.
+/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png
+INFO     GET https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png
+INFO     status_code: 200, response_time(ms): 83.98 ms, response_length: 15229 bytes
+
+.
+/account/sign_in
+INFO     POST https://testerhome.com/account/sign_in
+INFO     status_code: 200, response_time(ms): 172.8 ms, response_length: 89 bytes
+
+.
+/
+INFO     GET https://testerhome.com/
+INFO     status_code: 200, response_time(ms): 257.41 ms, response_length: 52463 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 4 tests in 0.722s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1555662601.html
+
+ + +

比较幸运,脚本在没有做任何修改的情况下运行成功了。

+

调试 & 优化测试用例

+

虽然脚本运行成功了,但是为了更好地管理和维护脚本,需要对脚本进行优化调整。

+

关联处理

+

查看录制生成的脚本,可以看到在发起登录请求时包含了 X-CSRF-Token,如果熟悉网络信息安全的基础知识,就会联想到该字段是动态变化的,每次都是先从服务器端返回至客户端,客户端在后续发起请求时需要携带该字段。

+
- test:
+    name: /account/sign_in
+    request:
+        data:
+            commit: Sign In
+            user[login]: debugtalk
+            user[password]: XXXXXXXX
+            user[remember_me]: '1'
+            utf8: 
+        headers:
+            Content-Type: application/x-www-form-urlencoded; charset=UTF-8
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+            X-CSRF-Token: 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA==
+            X-Requested-With: XMLHttpRequest
+        method: POST
+        url: https://testerhome.com/account/sign_in
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/html; charset=utf-8]
+
+ + +

虽然当前直接运行录制生成的脚本也是成功的,但很有可能在过了一段时间后,X-CSRF-Token 失效,脚本也就无法再成功运行了。因此在测试脚本中,该字段不能写死为抓包时获取的值,而是要每次动态地从前面的接口响应中获取。

+

那要怎么确定该字段是在之前的哪个接口中返回的呢?

+

操作方式也很简单,可以在抓包工具中对该字段进行搜索,特别地,搜索范围限定为响应内容(Response Header、Response Body)。

+

即可搜索得到该字段是在哪个接口中从服务器端返回值客户端的。

+

+

有时候可能搜索会得到多个结果,那么在确定是使用哪个接口响应的时候,遵循两个原则即可:

+
    +
  • 响应一定是出现在当前接口之前
  • +
  • 如果在当前接口之前存在多个接口均有此返回,那么取最靠近当前接口的即可
  • +
+

通过前面的搜索可知,X-CSRF-Token 的值是在第一个接口中响应返回的。

+

+

确定出具体的接口后,那么就可以在测试脚本中从该接口使用 extract 提取对应的字段,然后在后续接口中引用提取出的字段。

+

在当前案例中,第一个接口的响应内容为 HTML 页面,要提取字段可以使用正则匹配的方式。具体的做法就是指定目标字段的左右边界,目标字段使用 (.*) 匹配获取。

+
- test:
+    name: /account/sign_in
+    request:
+        headers:
+            If-None-Match: W/"bc9ae267fdcbd89bf1dfaea10dea2b0e"
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+        method: GET
+        url: https://testerhome.com/account/sign_in
+    extract:
+        X_CSRF_Token: <meta name="csrf-token" content="(.*)" />
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/html; charset=utf-8]
+
+ + +

然后,在后续使用到该字段的接口中,引用提取出的字段即可。

+
- test:
+    name: /account/sign_in
+    request:
+        data:
+            commit: Sign In
+            user[login]: debugtalk
+            user[password]: XXXXXXXX
+            user[remember_me]: '1'
+            utf8: 
+        headers:
+            Content-Type: application/x-www-form-urlencoded; charset=UTF-8
+            User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36
+                (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
+            X-CSRF-Token: $X_CSRF_Token
+            X-Requested-With: XMLHttpRequest
+        method: POST
+        url: https://testerhome.com/account/sign_in
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, text/html; charset=utf-8]
+
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/HttpRunner-architecture-diagram.svg b/images/HttpRunner-architecture-diagram.svg new file mode 100644 index 00000000..b6db8ac8 --- /dev/null +++ b/images/HttpRunner-architecture-diagram.svg @@ -0,0 +1 @@ +initializeadd_testsparsed testcaseparsed testcaseunittestTestSuiteload_testsvalidateparse_testsreportrunnerteststepiteration prepareHARCharles/FiddlerPostmancollectionrecord & generateJSONfile loaderapitestcasestestsuitesparse config nameparsed testcases(list)parse config requestJSON Schema ValidateRunner()Context()HttpSession()TestCasetestcase setup hookstestcase teardown hookscheck if skipprepare APIapi setup hookssend requestapi teardown hooksextract responsevalidateaggregateaggregate resultsgenerate html reportdefault templatecustom templatemake ResponseObjectYAML.envdebugtalk.pyRaw valid data.env validateimport builtin moduleparse config variablesparse config parametersextend & merge valid testcasesapi: validate, extract, variablestestcase: variablesaddTestCaseunittestTextTestRunner()TestLoader()parsed testcaseinitlocate debugtalk.py => PWDload custom.env / .envimport debugtalk.py module \ No newline at end of file diff --git a/images/charles-export-har.png b/images/charles-export-har.png new file mode 100644 index 00000000..82db6a05 Binary files /dev/null and b/images/charles-export-har.png differ diff --git a/images/charles-export.jpg b/images/charles-export.jpg new file mode 100644 index 00000000..a6d2ef7d Binary files /dev/null and b/images/charles-export.jpg differ diff --git a/images/charles-locate-response-token.png b/images/charles-locate-response-token.png new file mode 100644 index 00000000..edab6fa2 Binary files /dev/null and b/images/charles-locate-response-token.png differ diff --git a/images/charles-save-har.jpg b/images/charles-save-har.jpg new file mode 100644 index 00000000..1c86ed3c Binary files /dev/null and b/images/charles-save-har.jpg differ diff --git a/images/charles-search-response.png b/images/charles-search-response.png new file mode 100644 index 00000000..88c7e04a Binary files /dev/null and b/images/charles-search-response.png differ diff --git a/images/demo-quickstart-http-1.jpg b/images/demo-quickstart-http-1.jpg new file mode 100644 index 00000000..ae89002e Binary files /dev/null and b/images/demo-quickstart-http-1.jpg differ diff --git a/images/demo-quickstart-http-2.jpg b/images/demo-quickstart-http-2.jpg new file mode 100644 index 00000000..d831f096 Binary files /dev/null and b/images/demo-quickstart-http-2.jpg differ diff --git a/images/loadtest-schematic-diagram.jpg b/images/loadtest-schematic-diagram.jpg new file mode 100644 index 00000000..1d774c61 Binary files /dev/null and b/images/loadtest-schematic-diagram.jpg differ diff --git a/images/locusts-full-speed.jpg b/images/locusts-full-speed.jpg new file mode 100644 index 00000000..67efa3ea Binary files /dev/null and b/images/locusts-full-speed.jpg differ diff --git a/images/qrcode_for_httprunner.jpg b/images/qrcode_for_httprunner.jpg new file mode 100644 index 00000000..80687bbf Binary files /dev/null and b/images/qrcode_for_httprunner.jpg differ diff --git a/images/report-demo-quickstart-1-log1.jpg b/images/report-demo-quickstart-1-log1.jpg new file mode 100644 index 00000000..c390732a Binary files /dev/null and b/images/report-demo-quickstart-1-log1.jpg differ diff --git a/images/report-demo-quickstart-1-log2.jpg b/images/report-demo-quickstart-1-log2.jpg new file mode 100644 index 00000000..6eaddeae Binary files /dev/null and b/images/report-demo-quickstart-1-log2.jpg differ diff --git a/images/report-demo-quickstart-1-overview.jpg b/images/report-demo-quickstart-1-overview.jpg new file mode 100644 index 00000000..27a85fa5 Binary files /dev/null and b/images/report-demo-quickstart-1-overview.jpg differ diff --git a/images/report-demo-quickstart-1-traceback.jpg b/images/report-demo-quickstart-1-traceback.jpg new file mode 100644 index 00000000..a830102f Binary files /dev/null and b/images/report-demo-quickstart-1-traceback.jpg differ diff --git a/images/run-demo-quickstart-0.jpg b/images/run-demo-quickstart-0.jpg new file mode 100644 index 00000000..287634fc Binary files /dev/null and b/images/run-demo-quickstart-0.jpg differ diff --git a/images/run-demo-quickstart-1.jpg b/images/run-demo-quickstart-1.jpg new file mode 100644 index 00000000..34f72a60 Binary files /dev/null and b/images/run-demo-quickstart-1.jpg differ diff --git a/images/run-demo-quickstart-2.jpg b/images/run-demo-quickstart-2.jpg new file mode 100644 index 00000000..55fe4bc8 Binary files /dev/null and b/images/run-demo-quickstart-2.jpg differ diff --git a/images/run-demo-quickstart-6.jpg b/images/run-demo-quickstart-6.jpg new file mode 100644 index 00000000..40e69055 Binary files /dev/null and b/images/run-demo-quickstart-6.jpg differ diff --git a/images/testcase-layer.png b/images/testcase-layer.png new file mode 100644 index 00000000..bcb8b6f3 Binary files /dev/null and b/images/testcase-layer.png differ diff --git a/images/testcase-structure.png b/images/testcase-structure.png new file mode 100644 index 00000000..f63c0024 Binary files /dev/null and b/images/testcase-structure.png differ diff --git a/images/testerhome-charles-export.png b/images/testerhome-charles-export.png new file mode 100644 index 00000000..2a981acf Binary files /dev/null and b/images/testerhome-charles-export.png differ diff --git a/images/testerhome-login-charles.png b/images/testerhome-login-charles.png new file mode 100644 index 00000000..cbaf7d37 Binary files /dev/null and b/images/testerhome-login-charles.png differ diff --git a/images/testerhome-login.png b/images/testerhome-login.png new file mode 100644 index 00000000..b3fe28b9 Binary files /dev/null and b/images/testerhome-login.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..5469309c --- /dev/null +++ b/index.html @@ -0,0 +1,840 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

介绍

+ +

HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。

+

此文档适用于全新发布的 HttpRunner 2.x 版本,1.x 版本的使用文档请查看历史链接

+

设计理念

+
    +
  • 充分复用优秀的开源项目,不追求重复造轮子,而是将强大的轮子组装成战车
  • +
  • 遵循 约定大于配置 的准则,在框架功能中融入自动化测试最佳工程实践
  • +
  • 追求投入产出比,一份投入即可实现多种测试需求
  • +
+

核心特性

+
    +
  • 继承 Requests 的全部特性,轻松实现 HTTP(S) 的各种测试需求
  • +
  • 采用 YAML/JSON 的形式描述测试场景,保障测试用例描述的统一性和可维护性
  • +
  • 借助辅助函数(debugtalk.py),在测试脚本中轻松实现复杂的动态计算逻辑
  • +
  • 支持完善的测试用例分层机制,充分实现测试用例的复用
  • +
  • 测试前后支持完善的 hook 机制
  • +
  • 响应结果支持丰富的校验机制
  • +
  • 基于 HAR 实现接口录制和用例生成功能(har2case
  • +
  • 结合 Locust 框架,无需额外的工作即可实现分布式性能测试
  • +
  • 执行方式采用 CLI 调用,可与 Jenkins 等持续集成工具完美结合
  • +
  • 测试结果统计报告简洁清晰,附带详尽统计信息和日志记录
  • +
  • 极强的可扩展性,轻松实现二次开发和 Web 平台化
  • +
+

更多信息

+

关注 HttpRunner 的微信公众号,第一时间获得最新资讯。

+

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/slardar.js b/js/slardar.js new file mode 100644 index 00000000..f6ea9150 --- /dev/null +++ b/js/slardar.js @@ -0,0 +1,11 @@ + +(function(i,s,o,g,r,a,m){i["SlardarMonitorObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date;a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,"script","https://i.snssdk.com/slardar/sdk.js?bid=httprunner","Slardar"); +window.Slardar('config', { + sampleRate: 1, + bid: 'httprunner', + ignoreAjax: [], + ignoreStatic: [], + hookFetch: true, + enableSizeStats: true +}); +window.Slardar('send', 'pageview'); \ No newline at end of file diff --git a/prepare/dot-env/index.html b/prepare/dot-env/index.html new file mode 100644 index 00000000..6e839fa4 --- /dev/null +++ b/prepare/dot-env/index.html @@ -0,0 +1,963 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 环境变量 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+ +
+ + +
+
+ + + +

环境变量

+ +

环境变量的作用

+

在自动化测试中,有时需要借助环境变量实现某些特定的目的,常见的场景包括:

+
    +
  • 切换测试环境
  • +
  • 切换测试配置
  • +
  • 存储敏感数据(从信息安全的角度出发)
  • +
+

设置环境变量

+

在终端中预设环境变量

+

使用环境变量之前,需要先在系统中设置环境变量名称和值,传统的方式为使用 export 命令(Windows系统中使用 set 命令):

+
$ export UserName=admin
+$ echo $UserName
+admin
+$ export Password=123456
+$ echo $Password
+123456
+
+ + +

然后,在程序中就可以对系统中的环境变量进行读取。

+
$ python
+>>> import os
+>>> os.environ["UserName"]
+'admin'
+
+ + +

通过 .env 文件设置环境变量

+

除了这种方式,HttpRunner 还借鉴了 pipenv 加载 .env 的方式

+

默认情况下,在自动化测试项目的根目录中,创建 .env 文件,并将敏感数据信息放置到其中,存储采用 name=value 的格式:

+
$ cat .env
+UserName=admin
+Password=123456
+PROJECT_KEY=ABCDEFGH
+
+ + +

同时,.env 文件不应该添加到代码仓库中,建议将 .env 加入到 .gitignore 中。

+

HttpRunner 运行时,会自动将 .env 文件中的内容加载到运行时(RunTime)的环境变量中,然后在运行时中就可以对环境变量进行读取了。

+

若需加载不位于自动化项目根目录中的 .env,或者其它名称的 .env 文件(例如 production.env),可以采用 --dot-env-path 参数指定文件路径:

+
$ hrun /path/to/testcase.yml --dot-env-path /path/to/.env --log-level debug
+INFO     Loading environment variables from /path/to/.env
+DEBUG    Loaded variable: UserName
+DEBUG    Loaded variable: Password
+DEBUG    Loaded variable: PROJECT_KEY
+...
+
+ + +

引用环境变量

+

在 HttpRunner 中内置了函数 environ(简称 ENV),可用于在 YAML/JSON 脚本中直接引用环境变量。

+
- test:
+    name: login
+    request:
+        url: http://host/api/login
+        method: POST
+        headers:
+            Content-Type: application/json
+        json:
+            username: ${ENV(UserName)}
+            password: ${ENV(Password)}
+        validate:
+            - eq: [status_code, 200]
+
+ + +

若还需对读取的环境变量做进一步处理,则可以在 debugtalk.py 通过 Python 内置的函数 os.environ 对环境变量进行引用,然后再实现处理逻辑。

+

例如,若发起请求的密码需要先与密钥进行拼接并生成 MD5,那么就可以在 debugtalk.py 文件中实现如下函数:

+
import os
+
+def get_encrypt_password():
+    raw_passwd = os.environ["Password"]
+    PROJECT_KEY = os.environ["PROJECT_KEY"])
+    password = (raw_passwd + PROJECT_KEY).encode('ascii')
+    return hmac.new(password, hashlib.sha1).hexdigest()
+
+ + +

然后,在 YAML/JSON 格式的测试用例中,就可以通过${func()}的方式引用环境变量的值了。

+
- test:
+    name: login
+    request:
+        url: http://host/api/login
+        method: POST
+        headers:
+            Content-Type: application/json
+        json:
+            username: ${ENV(UserName)}
+            password: ${get_encrypt_password()}
+        validate:
+            - eq: [status_code, 200]
+
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/parameters/index.html b/prepare/parameters/index.html new file mode 100644 index 00000000..94141ff4 --- /dev/null +++ b/prepare/parameters/index.html @@ -0,0 +1,1494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 参数化数据驱动 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + + + +
+
+ + + +

参数化数据驱动

+ +

介绍

+

在自动化测试中,经常会遇到如下场景:

+
    +
  • 测试搜索功能,只有一个搜索输入框,但有 10 种不同类型的搜索关键字;
  • +
  • 测试账号登录功能,需要输入用户名和密码,按照等价类划分后有 20 种组合情况。
  • +
+

这里只是随意找了两个典型的例子,相信大家都有遇到过很多类似的场景。总结下来,就是在我们的自动化测试脚本中存在参数,并且我们需要采用不同的参数去运行。

+

经过概括,参数基本上分为两种类型:

+
    +
  • 单个独立参数:例如前面的第一种场景,我们只需要变换搜索关键字这一个参数
  • +
  • 多个具有关联性的参数:例如前面的第二种场景,我们需要变换用户名和密码两个参数,并且这两个参数需要关联组合
  • +
+

然后,对于参数而言,我们可能具有一个参数列表,在脚本运行时需要按照不同的规则去取值,例如顺序取值、随机取值、循环取值等等。

+

这就是典型的参数化和数据驱动。

+

如需了解 HttpRunner 参数化数据驱动机制的实现原理和技术细节,可前往阅读《HttpRunner 实现参数化数据驱动机制》

+

测试用例集(testsuite)准备

+

从 2.0.0 版本开始,HttpRunner 不再支持在测试用例文件中进行参数化配置;参数化的功能需要在 testsuite 中实现。变更的目的是让测试用例(testcase)的概念更纯粹,关于测试用例和测试用例集的概念定义,详见《测试用例组织》

+

参数化机制需要在测试用例集(testsuite)中实现。如需实现数据驱动机制,需要创建一个 testsuite,在 testsuite 中引用测试用例,并定义参数化配置。

+

测试用例集(testsuite)的格式如下所示:

+
config:
+    name: testsuite description
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+
+    testcase2_name:
+        testcase: /path/to/testcase2
+
+ + +

需要注意的是,testsuite 和 testcase 的格式存在较大区别,详见《测试用例组织》

+

参数配置概述

+

如需对某测试用例(testcase)实现参数化数据驱动,需要使用 parameters 关键字,定义参数名称并指定数据源取值方式。

+

参数名称的定义分为两种情况:

+
    +
  • 独立参数单独进行定义;
  • +
  • 多个参数具有关联性的参数需要将其定义在一起,采用短横线(-)进行连接。
  • +
+

数据源指定支持三种方式:

+
    +
  • 在 YAML/JSON 中直接指定参数列表:该种方式最为简单易用,适合参数列表比较小的情况
  • +
  • 通过内置的 parameterize(可简写为P)函数引用 CSV 文件:该种方式需要准备 CSV 数据文件,适合数据量比较大的情况
  • +
  • 调用 debugtalk.py 中自定义的函数生成参数列表:该种方式最为灵活,可通过自定义 Python 函数实现任意场景的数据驱动机制,当需要动态生成参数列表时也需要选择该种方式
  • +
+

三种方式可根据实际项目需求进行灵活选择,同时支持多种方式的组合使用。假如测试用例中定义了多个参数,那么测试用例在运行时会对参数进行笛卡尔积组合,覆盖所有参数组合情况。

+

使用方式概览如下:

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
+            user_id: ${P(user_id.csv)}
+            username-password: ${get_account(10)}
+
+ + +

参数配置详解

+

将参数名称定义和数据源指定方式进行组合,共有 6 种形式。现分别针对每一类情况进行详细说明。

+

独立参数 & 直接指定参数列表

+

对于参数列表比较小的情况,最简单的方式是直接在 YAML/JSON 中指定参数列表内容。

+

例如,对于独立参数 user_id,参数列表为 [1001, 1002, 1003, 1004],那么就可以按照如下方式进行配置:

+
config:
+    name: testcase description
+
+testcases:
+    create user:
+        testcase: demo-quickstart-6.yml
+        parameters:
+            user_id: [1001, 1002, 1003, 1004]
+
+ + +

进行该配置后,测试用例在运行时就会对 user_id 实现数据驱动,即分别使用 [1001, 1002, 1003, 1004] 四个值运行测试用例。

+
+点击查看运行日志 + +
$ hrun docs/data/demo-quickstart-7.json
+INFO     Start to run testcase: create user 1001
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes
+
+.
+/api/users/1001
+INFO     POST http://127.0.0.1:5000/api/users/1001
+INFO     status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.021s
+
+OK
+INFO     Start to run testcase: create user 1002
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes
+
+.
+/api/users/1002
+INFO     POST http://127.0.0.1:5000/api/users/1002
+INFO     status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.007s
+
+OK
+INFO     Start to run testcase: create user 1003
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes
+
+.
+/api/users/1003
+INFO     POST http://127.0.0.1:5000/api/users/1003
+INFO     status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.011s
+
+OK
+INFO     Start to run testcase: create user 1004
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes
+
+.
+/api/users/1004
+INFO     POST http://127.0.0.1:5000/api/users/1004
+INFO     status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.016s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html
+
+ + + +
+ +

可以看出,测试用例总共运行了 4 次,并且每次运行时都是采用的不同 user_id。

+

关联参数 & 直接指定参数列表

+

对于具有关联性的多个参数,例如 username 和 password,那么就可以按照如下方式进行配置:

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            username-password:
+                - ["user1", "111111"]
+                - ["user2", "222222"]
+                - ["user3", "333333"]
+
+ + +

进行该配置后,测试用例在运行时就会对 username 和 password 实现数据驱动,即分别使用 {"username": "user1", "password": "111111"}{"username": "user2", "password": "222222"}{"username": "user3", "password": "333333"} 运行 3 次测试,并且保证参数值总是成对使用。

+

独立参数 & 引用 CSV 文件

+

对于已有参数列表,并且数据量比较大的情况,比较适合的方式是将参数列表值存储在 CSV 数据文件中。

+

对于 CSV 数据文件,需要遵循如下几项约定的规则:

+
    +
  • CSV 文件中的第一行必须为参数名称,从第二行开始为参数值,每个(组)值占一行;
  • +
  • 若同一个 CSV 文件中具有多个参数,则参数名称和数值的间隔符需实用英文逗号;
  • +
  • 在 YAML/JSON 文件引用 CSV 文件时,文件路径为基于项目根目录(debugtalk.py 所在路径)的相对路径。
  • +
+

例如,user_id 的参数取值范围为 1001~2000,那么我们就可以创建 user_id.csv,并且在文件中按照如下形式进行描述。

+
user_id
+1001
+1002
+...
+1999
+2000
+
+ + +

然后在 YAML/JSON 测试用例文件中,就可以通过内置的 parameterize(可简写为 P)函数引用 CSV 文件。

+

假设项目的根目录下有 data 文件夹,user_id.csv 位于其中,那么 user_id.csv 的引用描述如下:

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            user_id: ${P(data/user_id.csv)}
+
+ + +

P 函数的参数(CSV 文件路径)是相对于项目根目录的相对路径。当然,这里也可以使用 CSV 文件在系统中的绝对路径,不过这样的话在项目路径变动时就会出现问题,因此推荐使用相对路径的形式。

+

关联参数 & 引用 CSV 文件

+

对于具有关联性的多个参数,例如 username 和 password,那么就可以创建 account.csv,并在文件中按照如下形式进行描述。

+
username,password
+test1,111111
+test2,222222
+test3,333333
+
+ + +

然后在 YAML/JSON 测试用例文件中,就可以通过内置的 parameterize(可简写为 P)函数引用 CSV 文件。

+

假设项目的根目录下有 data 文件夹,account.csv 位于其中,那么 account.csv 的引用描述如下:

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            username-password: ${P(data/account.csv)}
+
+ + +

需要说明的是,在 parameters 中指定的参数名称必须与 CSV 文件中第一行的参数名称一致,顺序可以不一致,参数个数也可以不一致。

+

例如,在 account.csv 文件中可以包含多个参数,username、password、phone、age:

+
username,password,phone,age
+test1,111111,18600000001,21
+test2,222222,18600000002,22
+test3,333333,18600000003,23
+
+ + +

而在 YAML/JSON 测试用例文件中指定参数时,可以只使用部分参数,并且参数顺序无需与 CSV 文件中参数名称的顺序一致。

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            phone-username: ${P(account.csv)}
+
+ + +

独立参数 & 引用自定义函数

+

对于没有现成参数列表,或者需要更灵活的方式动态生成参数的情况,可以通过在 debugtalk.py 中自定义函数生成参数列表,并在 YAML/JSON 引用自定义函数的方式。

+

例如,若需对 user_id 进行参数化数据驱动,参数取值范围为 1001~1004,那么就可以在 debugtalk.py 中定义一个函数,返回参数列表。

+
def get_user_id():
+    return [
+        {"user_id": 1001},
+        {"user_id": 1002},
+        {"user_id": 1003},
+        {"user_id": 1004}
+    ]
+
+ + +

然后,在 YAML/JSON 的 parameters 中就可以通过调用自定义函数的形式来指定数据源。

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            user_id: ${get_user_id()}
+
+ + +

另外,通过函数的传参机制,还可以实现更灵活的参数生成功能,在调用函数时指定需要生成的参数个数。

+

关联参数 & 引用自定义函数

+

对于具有关联性的多个参数,实现方式也类似。

+

例如,在 debugtalk.py 中定义函数 get_account,生成指定数量的账号密码参数列表。

+
def get_account(num):
+    accounts = []
+    for index in range(1, num+1):
+        accounts.append(
+            {"username": "user%s" % index, "password": str(index) * 6},
+        )
+
+    return accounts
+
+ + +

那么在 YAML/JSON 的 parameters 中就可以调用自定义函数生成指定数量的参数列表。

+
config:
+    name: "demo"
+
+testcases:
+    testcase1_name:
+        testcase: /path/to/testcase1
+        parameters:
+            username-password: ${get_account(10)}
+
+ + +
+

需要注意的是,在自定义函数中,生成的参数列表必须为 list of dict 的数据结构,该设计主要是为了与 CSV 文件的处理机制保持一致。

+
+

参数化运行

+

完成以上参数定义和数据源准备工作之后,参数化运行与普通测试用例的运行完全一致。

+

采用 hrun 命令运行自动化测试:

+
$ hrun tests/data/demo_parameters.yml
+
+ + +

采用 locusts 命令运行性能测试:

+
$ locusts -f tests/data/demo_parameters.yml
+
+ + +

区别在于,自动化测试时遍历一遍后会终止执行,性能测试时每个并发用户都会循环遍历所有参数。

+

案例演示

+

假设我们有一个获取 token 的测试用例

+
+点击查看 YAML 测试用例 + +
- config:
+    name: get token
+    base_url: http://127.0.0.1:5000
+    variables:
+        device_sn: ${gen_random_string(15)}
+        os_platform: 'ios'
+        app_version: '2.8.6'
+
+- test:
+    name: get token with $device_sn, $os_platform, $app_version
+    request:
+        headers:
+            Content-Type: application/json
+            User-Agent: python-requests/2.18.4
+            app_version: $app_version
+            device_sn: $device_sn
+            os_platform: $os_platform
+        json:
+            sign: ${get_sign($device_sn, $os_platform, $app_version)}
+        method: POST
+        url: /api/get-token
+    extract:
+        token: content.token
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, application/json]
+        - eq: [content.success, true]
+
+ + +
+ +

如果我们需要使用 device_sn、app_version 和 os_platform 这三个参数来进行参数化数据驱动,那么就可以创建一个 testsuite,并且进行参数化配置。

+
config:
+    name: get token with parameters
+
+testcases:
+    get token with $user_agent, $app_version, $os_platform:
+        testcase: demo-testcase-get-token.yml
+        parameters:
+            user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
+            app_version: ${P(app_version.csv)}
+            os_platform: ${get_os_platform()}
+
+ + +

其中,user_agent 使用了直接指定参数列表的形式。

+

app_version 通过 CSV 文件进行参数配置,对应的文件内容为:

+
app_version
+2.8.5
+2.8.6
+
+ + +

os_platform 使用自定义函数的形式生成参数列表,对应的函数内容为:

+
def get_os_platform():
+    return [
+        {"os_platform": "ios"},
+        {"os_platform": "android"}
+    ]
+
+ + +

那么,经过笛卡尔积组合,应该总共有 3*2*2=12 种参数组合情况。

+
+点击查看完整运行日志 + +
$ hrun docs/data/demo-parameters-get-token.yml
+INFO     Start to run testcase: get token with iOS/10.1, 2.8.5, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 10.66 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.026s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.1, 2.8.5, android
+get token with PBJda7SXM2ReWlu, android, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 3.03 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.004s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.1, 2.8.6, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 10.76 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.012s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.1, 2.8.6, android
+get token with PBJda7SXM2ReWlu, android, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 4.49 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.006s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.2, 2.8.5, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 4.39 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.006s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.2, 2.8.5, android
+get token with PBJda7SXM2ReWlu, android, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 4.04 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.005s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.2, 2.8.6, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 3.44 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.004s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.2, 2.8.6, android
+get token with PBJda7SXM2ReWlu, android, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 4.03 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.005s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.3, 2.8.5, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 5.14 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.008s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.3, 2.8.5, android
+get token with PBJda7SXM2ReWlu, android, 2.8.5
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 7.62 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.010s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.3, 2.8.6, ios
+get token with PBJda7SXM2ReWlu, ios, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 4.88 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.006s
+
+OK
+INFO     Start to run testcase: get token with iOS/10.3, 2.8.6, android
+get token with PBJda7SXM2ReWlu, android, 2.8.6
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 5.41 ms, response_length: 46 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.008s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1551950193.html
+
+ + +
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/project-structure/index.html b/prepare/project-structure/index.html new file mode 100644 index 00000000..d0c33fc1 --- /dev/null +++ b/prepare/project-structure/index.html @@ -0,0 +1,843 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 项目文件组织 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

项目文件组织

+ +

文件类型说明

+

在 HttpRunner 自动化测试项目中,主要存在如下几类文件:

+
    +
  • YAML/JSON(必须):测试用例文件,存储接口测试相关信息
  • +
  • debugtalk.py(可选):存储项目中逻辑运算辅助函数
      +
    • 该文件存在时,将作为项目根目录定位标记,其所在目录即被视为项目工程根目录
    • +
    • 该文件不存在时,运行测试的所在路径(CWD)将被视为项目工程根目录
    • +
    • 测试用例文件中的相对路径(例如.csv)均需基于项目工程根目录
    • +
    • 运行测试后,测试报告文件夹(reports)会生成在项目工程根目录
    • +
    +
  • +
  • .env(可选):存储项目环境变量,通常用于存储项目敏感信息
  • +
  • .csv(可选):项目数据文件,用于进行数据驱动
  • +
  • reports:默认生成测试报告的存储文件夹
  • +
+

项目文件结构

+

对于接口数比较少,或者测试场景比较简单的项目,组织测试用例时无需分层。在此种情况下,项目文件的目录结构没有任何要求,在项目中只需要一堆 YAML/JSON 文件即可,每一个文件单独对应一条测试用例;根据需要,项目中可能还会有 debugtalk.py.env等文件。

+

推荐的项目文件目录结构示例如下:

+
$ tree demo -a
+demo
+├── .env
+├── debugtalk.py
+├── reports
+├── testcase1.yml
+└── testcase2.json
+
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/record/index.html b/prepare/record/index.html new file mode 100644 index 00000000..4991e6af --- /dev/null +++ b/prepare/record/index.html @@ -0,0 +1,842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 录制生成用例 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

录制生成用例

+ +

为了简化测试用例的编写工作,HttpRunner 实现了测试用例生成的功能,对应的转换工具为一个独立的项目:har2case

+

简单来说,就是当前主流的抓包工具和浏览器都支持将抓取得到的数据包导出为标准通用的 HAR 格式(HTTP Archive),然后 HttpRunner 实现了将 HAR 格式的数据包转换为YAML/JSON格式的测试用例文件的功能。

+

获取 HAR 数据包

+

在转换生成测试用例之前,需要先将抓取得到的数据包导出为 HAR 格式的文件。在Charles Proxy中的操作方式为,选中需要转换的接口(可多选或全选),点击右键,在悬浮的菜单目录中点击【Export...】,格式选择HTTP Archive(.har)后保存即可;假设我们保存的文件名称为 demo.har。

+

+

+

转换生成测试用例

+

然后,在命令行终端中运行 har2case 命令,即可将 demo.har 转换为 HttpRunner 的测试用例文件。

+

使用 har2case 转换脚本时默认转换为 JSON 格式。

+
$ har2case docs/data/demo-quickstart.har
+INFO:root:Start to generate testcase.
+INFO:root:dump testcase to JSON format.
+INFO:root:Generate JSON testcase successfully: docs/data/demo-quickstart.json
+
+ + +

加上 -2y/--to-yml 参数后转换为 YAML 格式。

+
$ har2case docs/data/demo-quickstart.har -2y
+INFO:root:Start to generate testcase.
+INFO:root:dump testcase to YAML format.
+INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml
+
+ + +

两种格式完全等价,YAML 格式更简洁,JSON 格式支持的工具更丰富,大家可根据个人喜好进行选择。

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/request-hook/index.html b/prepare/request-hook/index.html new file mode 100644 index 00000000..55bc1513 --- /dev/null +++ b/prepare/request-hook/index.html @@ -0,0 +1,1036 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hook机制 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

hook机制

+ +

概述

+

HttpRunner 从 1.4.5 版本开始实现了全新的 hook 机制,可以在请求前和请求后调用钩子函数。

+

调用 hook 函数

+

hook 机制分为两个层级:

+
    +
  • 测试用例层面(testcase)
  • +
  • 测试步骤层面(teststep)
  • +
+

测试用例层面(testcase)

+

在 YAML/JSON 测试用例的 config 中新增关键字 setup_hooksteardown_hooks

+
    +
  • setup_hooks: 在整个用例开始执行前触发 hook 函数,主要用于准备工作。
  • +
  • teardown_hooks: 在整个用例结束执行后触发 hook 函数,主要用于测试后的清理工作。
  • +
+
- config:
+    name: basic test with httpbin
+    request:
+        base_url: http://127.0.0.1:3458/
+    setup_hooks:
+        - ${hook_print(setup)}
+    teardown_hooks:
+        - ${hook_print(teardown)}
+
+ + +

测试步骤层面(teststep)

+

在 YAML/JSON 测试步骤的 test 中新增关键字 setup_hooksteardown_hooks

+
    +
  • setup_hooks: 在 HTTP 请求发送前执行 hook 函数,主要用于准备工作;也可以实现对请求的 request 内容进行预处理。
  • +
  • teardown_hooks: 在 HTTP 请求发送后执行 hook 函数,主要用于测试后的清理工作;也可以实现对响应的 response 进行修改,例如进行加解密等处理。
  • +
+
"test": {
+    "name": "get token with $user_agent, $os_platform, $app_version",
+    "request": {
+        "url": "/api/get-token",
+        "method": "POST",
+        "headers": {
+            "app_version": "$app_version",
+            "os_platform": "$os_platform",
+            "user_agent": "$user_agent"
+        },
+        "json": {
+            "sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
+        }
+    },
+    "validate": [
+        {"eq": ["status_code", 200]}
+    ],
+    "setup_hooks": [
+        "${setup_hook_prepare_kwargs($request)}",
+        "${setup_hook_httpntlmauth($request)}"
+    ],
+    "teardown_hooks": [
+        "${teardown_hook_sleep_N_secs($response, 2)}"
+    ]
+}
+
+ + +

编写 hook 函数

+

hook 函数的定义放置在项目的 debugtalk.py 中,在 YAML/JSON 中调用 hook 函数仍然是采用 ${func($a, $b)} 的形式。

+

对于测试用例层面的 hook 函数,与 YAML/JSON 中自定义的函数完全相同,可通过自定义参数传参的形式来实现灵活应用。

+
def hook_print(msg):
+    print(msg)
+
+ + +

对于单个测试用例层面的 hook 函数,除了可传入自定义参数外,还可以传入与当前测试用例相关的信息,包括请求的 $request 和响应的 $response,用于实现更复杂场景的灵活应用。

+

setup_hooks

+

在测试步骤层面的 setup_hooks 函数中,除了可传入自定义参数外,还可以传入 $request,该参数对应着当前测试步骤 request 的全部内容。因为 request 是可变参数类型(dict),因此该函数参数为引用传递,当我们需要对请求参数进行预处理时尤其有用。

+

e.g.

+
def setup_hook_prepare_kwargs(request):
+    if request["method"] == "POST":
+        content_type = request.get("headers", {}).get("content-type")
+        if content_type and "data" in request:
+            # if request content-type is application/json, request data should be dumped
+            if content_type.startswith("application/json") and isinstance(request["data"], (dict, list)):
+                request["data"] = json.dumps(request["data"])
+
+            if isinstance(request["data"], str):
+                request["data"] = request["data"].encode('utf-8')
+
+def setup_hook_httpntlmauth(request):
+    if "httpntlmauth" in request:
+        from requests_ntlm import HttpNtlmAuth
+        auth_account = request.pop("httpntlmauth")
+        request["auth"] = HttpNtlmAuth(
+            auth_account["username"], auth_account["password"])
+
+ + +

通过上述的 setup_hook_prepare_kwargs 函数,可以实现根据请求方法和请求的 Content-Type 来对请求的 data 进行加工处理;通过 setup_hook_httpntlmauth 函数,可以实现 HttpNtlmAuth 权限授权。

+

teardown_hooks

+

在测试步骤层面的 teardown_hooks 函数中,除了可传入自定义参数外,还可以传入 $response,该参数对应着当前请求的响应实例(requests.Response)。

+

e.g.

+
def teardown_hook_sleep_N_secs(response, n_secs):
+    """ sleep n seconds after request
+    """
+    if response.status_code == 200:
+        time.sleep(0.1)
+    else:
+        time.sleep(n_secs)
+
+ + +

通过上述的 teardown_hook_sleep_N_secs 函数,可以根据接口响应的状态码来进行不同时间的延迟等待。

+

另外,在 teardown_hooks 函数中还可以对 response 进行修改。当我们需要先对响应内容进行处理(例如加解密、参数运算),再进行参数提取(extract)和校验(validate)时尤其有用。

+

例如在下面的测试步骤中,在执行测试后,通过 teardown_hooks 函数将响应结果的状态码和 headers 进行了修改,然后再进行了校验。

+
- test:
+    name: alter response
+    request:
+        url: /headers
+        method: GET
+    teardown_hooks:
+        - ${alter_response($response)}
+    validate:
+        - eq: ["status_code", 500]
+        - eq: ["headers.content-type", "html/text"]
+
+ + +
def alter_response(response):
+    response.status_code = 500
+    response.headers["Content-Type"] = "html/text"
+
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/security/index.html b/prepare/security/index.html new file mode 100644 index 00000000..5de7fd11 --- /dev/null +++ b/prepare/security/index.html @@ -0,0 +1,832 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 信息安全 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

信息安全

+ +

背景

+

很多时候项目代码在运行时需要使用到账号、密码、key等敏感数据信息,但是从信息安全的角度考虑,我们是不能将这些敏感数据提交到代码仓库的,主要原因有两个:

+
    +
  • 加强权限管控:参与项目的开发人员可能会有很多,大家都有读取代码仓库的权限,但是像 key 这类极度敏感的信息不应该所有人都有权限获取;
  • +
  • 减少代码泄漏的危害性:假如代码出现泄漏,敏感数据信息不应该也同时泄漏。
  • +
+

解决方案

+

那代码部署到服务器或 Jenkins 执行机后,运行时要使用到这些敏感数据信息,该怎么操作呢?

+

推荐的操作方式为:

+
    +
  • 对服务器进行权限管控,只有运维人员(或者核心开发人员)才有登录服务器的权限;
  • +
  • 运维人员(或者核心开发人员):在运行的机器上将敏感数据设置到系统的环境变量中;
  • +
  • 普通开发人员:只需要知道敏感信息的变量名称,在代码中通过读取环境变量的方式获取敏感数据。
  • +
+

存储敏感数据(设置环境变量)和使用敏感数据(引用环境变量)的具体方法,可参考环境变量使用说明文档。

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/testcase-layer/index.html b/prepare/testcase-layer/index.html new file mode 100644 index 00000000..6df39e35 --- /dev/null +++ b/prepare/testcase-layer/index.html @@ -0,0 +1,1213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 测试用例分层 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + + + +
+
+ + + +

测试用例分层

+ +

测试用例分层模型

+

在自动化测试领域,自动化测试用例的可维护性是极其重要的因素,直接关系到自动化测试能否持续有效地在项目中开展。

+

概括来说,测试用例分层机制的核心是将接口定义、测试步骤、测试用例、测试场景进行分离,单独进行描述和维护,从而尽可能地减少自动化测试用例的维护成本。

+

逻辑关系图如下所示:

+

+

同时,强调如下几点核心概念:

+
    +
  • 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
  • +
  • 测试用例是测试步骤(teststep)的 有序 集合,每一个测试步骤对应一个 API 的请求描述
  • +
  • 测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
  • +
+

如果对于上述第三点感觉难以理解,不妨看下上图中的示例:

+
    +
  • testcase1 依赖于 testcase2,那么就可以在测试步骤(teststep12)中对 testcase2 进行引用,然后 testcase1 就是完整且可独立运行的;
  • +
  • 在 testsuite 中,testcase1 与 testcase2 相互独立,运行顺序就不再有先后依赖关系了。
  • +
+

分层描述详解

+

理解了测试用例分层模型,接下来我们再来看下在分层模型下,接口、测试用例、测试用例集的描述形式。

+

接口定义(API)

+

为了更好地对接口描述进行管理,推荐使用独立的文件对接口描述进行存储,即每个文件对应一个接口描述。

+

接口定义描述的主要内容包括:name、variables、request、base_url、validate 等,形式如下:

+
name: get headers
+base_url: http://httpbin.org
+variables:
+    expected_status_code: 200
+request:
+    url: /headers
+    method: GET
+validate:
+    - eq: ["status_code", $expected_status_code]
+    - eq: [content.headers.Host, "httpbin.org"]
+
+ + +

其中,name 和 request 部分是必须的,request 中的描述形式与 requests.request 完全相同。

+

另外,API 描述需要尽量保持完整,做到可以单独运行。如果在接口描述中存在变量引用的情况,可在 variables 中对参数进行定义。通过这种方式,可以很好地实现单个接口的调试。

+
$ hrun api/get_headers.yml
+INFO     Start to run testcase: get headers
+headers
+INFO     GET http://httpbin.org/headers
+INFO     status_code: 200, response_time(ms): 477.32 ms, response_length: 157 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 1 test in 0.478s
+
+OK
+
+ + +

测试用例(testcase)

+

引用接口定义

+

有了接口的定义描述后,我们编写测试场景时就可以直接引用接口定义了。

+

在测试步骤(teststep)中,可通过 api 字段引用接口定义,引用方式为对应 API 文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 debugtalk.py 所在的目录路径。

+
- config:
+    name: "setup and reset all."
+    variables:
+        user_agent: 'iOS/10.3'
+        device_sn: "TESTCASE_SETUP_XXX"
+        os_platform: 'ios'
+        app_version: '2.8.6'
+    base_url: "http://127.0.0.1:5000"
+    verify: False
+    output:
+        - session_token
+
+- test:
+    name: get token (setup)
+    api: api/get_token.yml
+    variables:
+        user_agent: 'iOS/10.3'
+        device_sn: $device_sn
+        os_platform: 'ios'
+        app_version: '2.8.6'
+    extract:
+        - session_token: content.token
+    validate:
+        - eq: ["status_code", 200]
+        - len_eq: ["content.token", 16]
+
+- test:
+    name: reset all users
+    api: api/reset_all.yml
+    variables:
+        token: $session_token
+
+ + +

若需要控制或改变接口定义中的参数值,可在测试步骤中指定 variables 参数,覆盖 API 中的 variables 实现。

+

同样地,在测试步骤中定义 validate 后,也会与 API 中的 validate 合并覆盖。因此推荐的做法是,在 API 定义中的 validate 只描述最基本的校验项,例如 status_code,对于与业务逻辑相关的更多校验项,在测试步骤的 validate 中进行描述。

+

引用测试用例

+

在测试用例的测试步骤中,除了可以引用接口定义,还可以引用其它测试用例。通过这种方式,可以在避免重复描述的同时,解决测试用例的依赖关系,从而保证每个测试用例都是独立可运行的。

+

在测试步骤(teststep)中,可通过 testcase 字段引用其它测试用例,引用方式为对应测试用例文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 debugtalk.py 所在的目录路径。

+

例如,在上面的测试用例("setup and reset all.")中,实现了对获取 token 功能的测试;同时,在很多其它功能中都会依赖于获取 token 的功能,如果将该功能的测试步骤脚本拷贝到其它功能的测试用例中,那么就会存在大量重复,当需要对该部分进行修改时就需要修改所有地方,显然不便于维护。

+

比较好的做法是,在其它功能的测试用例(如创建用户)中,引用获取 token 功能的测试用例(testcases/setup.yml)作为一个测试步骤,从而创建用户("create user and check result.")这个测试用例也变得独立可运行了。

+
- config:
+    name: "create user and check result."
+    id: create_user
+    base_url: "http://127.0.0.1:5000"
+    variables:
+        uid: 9001
+        device_sn: "TESTCASE_CREATE_XXX"
+    output:
+        - session_token
+
+- test:
+    name: setup and reset all (override) for $device_sn.
+    testcase: testcases/setup.yml
+    output:
+        - session_token
+
+- test:
+    name: create user and check result.
+    variables:
+        token: $session_token
+    testcase: testcases/deps/check_and_create.yml
+
+ + +

测试用例集(testsuite)

+

当测试用例数量比较多以后,为了方便管理和实现批量运行,通常需要使用测试用例集来对测试用例进行组织。

+

在前文的测试用例分层模型中也强调了,测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理。

+

因为是 无序 集合,因此测试用例集的描述形式会与测试用例有些不同,在每个测试用例集文件中,第一层级存在两类字段:

+
    +
  • config: 测试用例集的总体配置参数
  • +
  • testcases: 值为字典结构(无序),key 为测试用例的名称,value 为测试用例的内容;在引用测试用例时也可以指定 variables,实现对引用测试用例中 variables 的覆盖。
  • +
+

非参数化场景

+
config:
+    name: create users with uid
+    variables:
+        device_sn: ${gen_random_string(15)}
+        var_a: ${gen_random_string(5)}
+        var_b: $var_a
+    base_url: "http://127.0.0.1:5000"
+
+testcases:
+    create user 1000 and check result.:
+        testcase: testcases/create_user.yml
+        variables:
+            uid: 1000
+            var_c: ${gen_random_string(5)}
+            var_d: $var_c
+
+    create user 1001 and check result.:
+        testcase: testcases/create_user.yml
+        variables:
+            uid: 1001
+            var_c: ${gen_random_string(5)}
+            var_d: $var_c
+
+ + +

参数化场景(parameters)

+

对于参数化场景,可通过 parameters 实现,描述形式如下所示。

+
config:
+    name: create users with parameters
+    variables:
+        device_sn: ${gen_random_string(15)}
+    base_url: "http://127.0.0.1:5000"
+
+testcases:
+    create user $uid and check result for $device_sn.:
+        testcase: testcases/create_user.yml
+        variables:
+            uid: 1000
+            device_sn: TESTSUITE_XXX
+        parameters:
+            uid: [101, 102, 103]
+            device_sn: [TESTSUITE_X1, TESTSUITE_X2]
+
+ + +

参数化后,parameters 中的变量将采用笛卡尔积组合形成参数列表,依次覆盖 variables 中的参数,驱动测试用例的运行。

+

文件目录结构管理 && 脚手架工具

+

在对测试用例文件进行组织管理时,对于文件的存储位置均没有要求和限制,在引用时只需要指定对应的文件路径即可。但从约定大于配置的角度,最好是按照推荐的文件夹名称进行存储管理,并可通过子目录实现项目模块分类管理。

+

推荐的方式汇总如下:

+
    +
  • debugtalk.py 放置在项目根目录下,假设为 PRJ_ROOT_DIR
  • +
  • .env 放置在项目根目录下,路径为 PRJ_ROOT_DIR/.env
  • +
  • 接口定义(API)放置在 PRJ_ROOT_DIR/api/ 目录下
  • +
  • 测试用例(testcase)放置在 PRJ_ROOT_DIR/testcases/ 目录下
  • +
  • 测试用例集(testsuite)文件必须放置在 PRJ_ROOT_DIR/testsuites/ 目录下
  • +
  • data 文件夹:存储参数化文件,或者项目依赖的文件,路径为 PRJ_ROOT_DIR/data/
  • +
  • reports 文件夹:存储 HTML 测试报告,生成路径为 PRJ_ROOT_DIR/reports/
  • +
+

目录结构如下所示:

+
$ tree tests
+tests
+├── .env
+├── data
+│   ├── app_version.csv
+│   └── account.csv
+├── api
+│   ├── create_user.yml
+│   ├── get_headers.yml
+│   ├── get_token.yml
+│   ├── get_user.yml
+│   └── reset_all.yml
+├── debugtalk.py
+├── testcases
+│   ├── create_user.yml
+│   ├── deps
+│   │   └── check_and_create.yml
+│   └── setup.yml
+└── testsuites
+    ├── create_users.yml
+    └── create_users_with_parameters.yml
+
+ + +

项目脚手架

+

同时,在 HttpRunner 中实现了一个脚手架工具,可以快速创建项目的目录结构。该想法来源于 Djangodjango-admin.py startproject project_name

+

使用方式也与 Django 类似,只需要通过 --startproject 指定新项目的名称即可。

+
$ hrun --startproject demo
+Start to create new project: demo
+CWD: /Users/debugtalk/MyProjects/examples
+
+created folder: demo
+created folder: demo/api
+created folder: demo/testcases
+created folder: demo/testsuites
+created folder: demo/reports
+created file: demo/debugtalk.py
+created file: demo/.env
+
+ + +

相关参考

+ + + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/testcase-structure/index.html b/prepare/testcase-structure/index.html new file mode 100644 index 00000000..cb882958 --- /dev/null +++ b/prepare/testcase-structure/index.html @@ -0,0 +1,1111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 测试用例组织 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

测试用例组织

+ +

YAML & JSON

+

HttpRunner 的测试用例支持两种文件格式:YAML 和 JSON。

+

JSON 和 YAML 格式的测试用例完全等价,包含的信息内容也完全相同。

+
    +
  • 对于新手来说,推荐使用 JSON 格式,虽然描述形式上稍显累赘,但是不容易出错(大多编辑器都具有 JSON 格式的检测功能);同时,HttpRunner 也内置了 JSON 格式正确性检测和样式美化功能,详情可查看《Validate & Prettify》
  • +
  • 对于熟悉 YAML 格式的人来说,编写维护 YAML 格式的测试用例会更简洁,但前提是要保证 YAML 格式没有语法错误。
  • +
+

对于两种格式的展示差异,可以对比查看 demo-quickstart-6.jsondemo-quickstart-6.yml 获取初步的印象。

+

后面为了更清晰的描述,统一采用 JSON 格式作为示例。

+

测试用例结构

+

testcase-structure

+

在 HttpRunner 中,测试用例组织主要基于三个概念:

+
    +
  • 测试用例集(testsuite):对应一个文件夹,包含单个或多个测试用例(YAML/JSON)文件
  • +
  • 测试用例(testcase):对应一个 YAML/JSON 文件,包含单个或多个测试步骤
  • +
  • 测试步骤(teststep):对应 YAML/JSON 文件中的一个 test,描述单次接口测试的全部内容,包括发起接口请求、解析响应结果、校验结果等
  • +
+

对于单个 YAML/JSON 文件来说,数据存储结构为 list of dict 的形式,其中可能包含一个全局配置项(config)和若干个测试步骤(test)。

+

具体地:

+
    +
  • config:作为整个测试用例的全局配置项
  • +
  • test:对应单个测试步骤(teststep),测试用例存在顺序关系,运行时将从前往后依次运行各个测试步骤
  • +
+

对应的 JSON 格式如下所示:

+
[
+  {
+    "config": {...}
+  },
+  {
+    "test": {...}
+  },
+  {
+    "test": {...}
+  }
+]
+
+ + +

变量空间(context)作用域

+

在测试用例内部,HttpRunner 划分了两层变量空间作用域(context)。

+
    +
  • config:作为整个测试用例的全局配置项,作用域为整个测试用例;
  • +
  • test:测试步骤的变量空间(context)会继承或覆盖 config 中定义的内容;
      +
    • 若某变量在 config 中定义了,在某 test 中没有定义,则该 test 会继承该变量
    • +
    • 若某变量在 config 和某 test 中都定义了,则该 test 中使用自己定义的变量值
    • +
    +
  • +
  • 各个测试步骤(test)的变量空间相互独立,互不影响;
  • +
  • 如需在多个测试步骤(test)中传递参数值,则需要使用 extract 关键字,并且只能从前往后传递
  • +
+

config

+
"config": {
+    "name": "testcase description",
+    "parameters": [
+        {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
+        {"app_version": "${P(app_version.csv)}"},
+        {"os_platform": "${get_os_platform()}"}
+    ],
+    "variables": [
+        {"user_agent": "iOS/10.3"},
+        {"device_sn": "${gen_random_string(15)}"},
+        {"os_platform": "ios"}
+    ],
+    "request": {
+        "base_url": "http://127.0.0.1:5000",
+        "headers": {
+            "Content-Type": "application/json",
+            "device_sn": "$device_sn"
+        }
+    },
+    "output": [
+        "token"
+    ]
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Keyrequired?formatdescrption
nameYesstring测试用例的名称,在测试报告中将作为标题
variablesNolist of dict定义的全局变量,作用域为整个用例
parametersNolist of dict全局参数,用于实现数据化驱动,作用域为整个用例
requestNodictrequest 的公共参数,作用域为整个用例;常用参数包括 base_url 和 headers
+

request

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Keyrequired?formatdescrption
base_urlNostring测试用例请求 URL 的公共 host,指定该参数后,test 中的 url 可以只描述 path 部分
headersNodictrequest 中 headers 的公共参数,作用域为整个用例
outputNolist整个用例输出的参数列表,可输出的参数包括公共的 variable 和 extract 的参数; 在 log-level 为 debug 模式下,会在 terminal 中打印出参数内容
+

test

+
"test": {
+    "name": "get token with $user_agent, $os_platform, $app_version",
+    "request": {
+        "url": "/api/get-token",
+        "method": "POST",
+        "headers": {
+            "app_version": "$app_version",
+            "os_platform": "$os_platform",
+            "user_agent": "$user_agent"
+        },
+        "json": {
+            "sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
+        },
+        "extract": [
+            {"token": "content.token"}
+        ],
+        "validate": [
+            {"eq": ["status_code", 200]},
+            {"eq": ["headers.Content-Type", "application/json"]},
+            {"eq": ["content.success", true]}
+        ],
+        "setup_hooks": [],
+        "teardown_hooks": []
+    }
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Keyrequired?formatdescrption
nameYesstring测试步骤的名称,在测试报告中将作为测试步骤的名称
requestYesdictHTTP 请求的详细内容;可用参数详见 python-requests 官方文档
variablesNolist of dict测试步骤中定义的变量,作用域为当前测试步骤
extractNolist从当前 HTTP 请求的响应结果中提取参数,并保存到参数变量中(例如token),后续测试用例可通过$token的形式进行引用
validateNolist测试用例中定义的结果校验项,作用域为当前测试用例,用于实现对当前测试用例运行结果的校验
setup_hooksNolist在 HTTP 请求发送前执行 hook 函数,主要用于准备工作
teardown_hooksNolist在 HTTP 请求发送后执行 hook 函数,主要用户测试后的清理工作
+

extract

+

支持多种提取方式:

+
    +
  • 响应结果为 JSON 结构,可采用.运算符的方式,例如headers.Content-Typecontent.success
  • +
  • 响应结果为 text/html 结构,可采用正则表达式的方式,例如blog-motto\">(.*)</h2>
  • +
  • 详情可阅读《ApiTestEngine,不再局限于API的测试》
  • +
+

validate

+

支持两种格式:

+
    +
  • {"comparator_name": [check_item, expect_value]}
  • +
  • {"check": check_item, "comparator": comparator_name, "expect": expect_value}
  • +
+

hooks

+

setup_hooks 函数放置于 debugtalk.py 中,并且必须包含三个参数:

+
    +
  • method: 请求方法,e.g. GET, POST, PUT
  • +
  • url: 请求 URL
  • +
  • kwargs: request 的参数字典
  • +
+

teardown_hooks 函数放置于 debugtalk.py 中,并且必须包含一个参数:

+
    +
  • resp_obj: requests.Response 实例
  • +
+

关于 setup_hooksteardown_hooks 的更多内容,请参考《hook 机制》

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prepare/validate-pretty/index.html b/prepare/validate-pretty/index.html new file mode 100644 index 00000000..db26e4f4 --- /dev/null +++ b/prepare/validate-pretty/index.html @@ -0,0 +1,874 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Validate & Prettify - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

Validate & Prettify

+ +

HttpRunner 从 1.3.1 版本开始,支持对 JSON 格式测试用例的内容进行格式正确性检测和样式美化功能。

+

JSON 格式正确性检测

+

若需对 JSON 格式用例文件的内容进行正确性检测,可使用 --validate 参数。

+

可指定单个 JSON 用例文件路径。

+
$ hrun --validate docs/data/demo-quickstart.json
+Start to validate JSON file: docs/data/demo-quickstart.json
+OK
+
+ + +

也可指定多个 JSON 用例文件路径。

+
$ hrun --validate docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json
+Start to validate JSON file: docs/data/demo-quickstart.json
+OK
+WARNING  Only JSON file format can be validated, skip docs/data/demo-quickstart.yml
+Start to validate JSON file: docs/data/demo-quickstart-0.json
+OK
+
+ + +

如上所示,当传入的文件后缀不是.json,HttpRunner 会打印 WARNING 信息,并跳过检测。

+

若 JSON 文件格式正确,则打印 OK。

+

若 JSON 文件格式存在异常,则打印详细的报错信息,精确到错误在文件中出现的行和列。

+
$ hrun --validate docs/data/demo-quickstart.json
+Start to validate JSON file: docs/data/demo-quickstart.json
+Expecting ',' delimiter: line 5 column 13 (char 82)
+
+ + +

JSON 格式美化

+

与 YAML 格式不同,JSON 格式不强制要求缩进和换行,这有点类似于 C 语言和 Python 语言的差异。

+

例如,demo-quickstart.json文件也可以改写为如下形式。

+
[{"config": {"name": "testcase description","variables": [],"request": {"base_url": "","headers": {"User-Agent": "python-requests/2.18.4"}}}},{"test": {"name": "/api/get-token","request": {"url": "http://127.0.0.1:5000/api/get-token","headers": {"device_sn": "FwgRiO7CNA50DSU","user_agent": "iOS/10.3","os_platform": "ios","app_version": "2.8.6","Content-Type": "application/json"},"method": "POST","json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"}},"validate": [{"eq": ["status_code",200]},{"eq": ["headers.Content-Type","application/json"]},{"eq": ["content.success",true]},{"eq": ["content.token","baNLX1zhFYP11Seb"]}]}},{"test": {"name": "/api/users/1000","request": {"url": "http://127.0.0.1:5000/api/users/1000","headers": {"device_sn": "FwgRiO7CNA50DSU","token": "baNLX1zhFYP11Seb","Content-Type": "application/json"},"method": "POST","json": {"name": "user1","password": "123456"}},"validate": [{"eq": ["status_code",201]},{"eq": ["headers.Content-Type","application/json"]},{"eq": ["content.success",true]},{"eq": ["content.msg","user created successfully."]}]}}]
+
+ + +

虽然上面 JSON 格式的测试用例也能正常执行,但测试用例文件的可读性太差,不利于阅读和维护。

+

针对该需求,可使用 --prettify 参数对 JSON 格式用例文件进行样式美化。

+

可指定单个 JSON 用例文件路径。

+
$ hrun --prettify docs/data/demo-quickstart.json
+Start to prettify JSON file: docs/data/demo-quickstart.json
+success: docs/data/demo-quickstart.pretty.json
+
+ + +

也可指定多个 JSON 用例文件路径。

+
$ hrun --prettify docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json
+WARNING  Only JSON file format can be prettified, skip: docs/data/demo-quickstart.yml
+Start to prettify JSON file: docs/data/demo-quickstart.json
+success: docs/data/demo-quickstart.pretty.json
+Start to prettify JSON file: docs/data/demo-quickstart-0.json
+success: docs/data/demo-quickstart-0.pretty.json
+
+ + +

如上所示,当传入的文件后缀不是.json,HttpRunner 会打印 WARNING 信息,并跳过检测。

+

若转换成功,则打印美化后的文件路径;若 JSON 文件格式存在异常,则打印详细的报错信息,精确到错误在文件中出现的行和列。

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstart/index.html b/quickstart/index.html new file mode 100644 index 00000000..73f0d348 --- /dev/null +++ b/quickstart/index.html @@ -0,0 +1,1551 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 快速上手 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + + + + +
+
+ + + +

快速上手

+ +

本文将通过一个简单的示例来展示 HttpRunner 的核心功能使用方法。

+

案例介绍

+

该案例作为被测服务,主要有两类接口:

+
    +
  • 权限校验,获取 token
  • +
  • 支持 CRUD 操作的 RESTful APIs,所有接口的请求头域中都必须包含有效的 token
  • +
+

案例的实现形式为 flask 应用服务(api_server.py),启动方式如下:

+
$ export FLASK_APP=docs/data/api_server.py
+$ export FLASK_ENV=development
+$ flask run
+ * Serving Flask app "docs/data/api_server.py" (lazy loading)
+ * Environment: development
+ * Debug mode: on
+ * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
+ * Restarting with stat
+ * Debugger is active!
+ * Debugger PIN: 989-476-348
+
+ + +

服务启动成功后,我们就可以开始对其进行测试了。

+

测试准备

+

抓包分析

+

在开始测试之前,我们需要先了解接口的请求和响应细节,而最佳的方式就是采用 Charles Proxy 或者 Fiddler 这类网络抓包工具进行抓包分析。

+

例如,在本案例中,我们先进行权限校验,然后成功创建一个用户,对应的网络抓包内容如下图所示:

+

+

+

通过抓包,我们可以看到具体的接口信息,包括请求的URL、Method、headers、参数和响应内容等内容,基于这些信息,我们就可以开始编写测试用例了。

+

生成测试用例

+

为了简化测试用例的编写工作,HttpRunner 实现了测试用例生成的功能。

+

首先,需要将抓取得到的数据包导出为 HAR 格式的文件,假设导出的文件名称为 demo-quickstart.har

+

然后,在命令行终端中运行如下命令,即可将 demo-quickstart.har 转换为 HttpRunner 的测试用例文件。

+
$ har2case docs/data/demo-quickstart.har -2y
+INFO:root:Start to generate testcase.
+INFO:root:dump testcase to YAML format.
+INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml
+
+ + +

使用 har2case 转换脚本时默认转换为 JSON 格式,加上 -2y 参数后转换为 YAML 格式。两种格式完全等价,YAML 格式更简洁,JSON 格式支持的工具更丰富,大家可根据个人喜好进行选择。关于 har2case 的详细使用说明,请查看《录制生成测试用例》

+

经过转换,在源 demo-quickstart.har 文件的同级目录下生成了相同文件名称的 YAML 格式测试用例文件 demo-quickstart.yml,其内容如下:

+
- config:
+    name: testcase description
+    variables: {}
+
+- test:
+    name: /api/get-token
+    request:
+        headers:
+            Content-Type: application/json
+            User-Agent: python-requests/2.18.4
+            app_version: 2.8.6
+            device_sn: FwgRiO7CNA50DSU
+            os_platform: ios
+        json:
+            sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+        method: POST
+        url: http://127.0.0.1:5000/api/get-token
+    validate:
+        - eq: [status_code, 200]
+        - eq: [headers.Content-Type, application/json]
+        - eq: [content.success, true]
+        - eq: [content.token, baNLX1zhFYP11Seb]
+
+- test:
+    name: /api/users/1000
+    request:
+        headers:
+            Content-Type: application/json
+            User-Agent: python-requests/2.18.4
+            device_sn: FwgRiO7CNA50DSU
+            token: baNLX1zhFYP11Seb
+        json:
+            name: user1
+            password: '123456'
+        method: POST
+        url: http://127.0.0.1:5000/api/users/1000
+    validate:
+        - eq: [status_code, 201]
+        - eq: [headers.Content-Type, application/json]
+        - eq: [content.success, true]
+        - eq: [content.msg, user created successfully.]
+
+ + +

现在我们只需要知道如下几点:

+
    +
  • 每个 YAML/JSON 文件对应一个测试用例(testcase)
  • +
  • 每个测试用例为一个list of dict结构,其中可能包含全局配置项(config)和若干个测试步骤(test)
  • +
  • config 为全局配置项,作用域为整个测试用例
  • +
  • test 对应单个测试步骤,作用域仅限于本身
  • +
+

如上便是 HttpRunner 测试用例的基本结构。

+

关于测试用例的更多内容,请查看《测试用例结构描述》

+

首次运行测试用例

+

测试用例就绪后,我们可以开始调试运行了。

+

为了演示测试用例文件的迭代优化过程,我们先将 demo-quickstart.json 重命名为 demo-quickstart-0.json(对应的 YAML 格式:demo-quickstart-0.yml)。

+

运行测试用例的命令为hrun,后面直接指定测试用例文件的路径即可。

+
$ hrun docs/data/demo-quickstart-0.yml
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 9.26 ms, response_length: 46 bytes
+
+ERROR    validate: content.token equals baNLX1zhFYP11Seb(str)   ==> fail
+tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
+ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
+====== request details ======
+url: http://127.0.0.1:5000/api/get-token
+method: POST
+headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios'}
+json: {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'}
+verify: True
+
+====== response details ======
+status_code: 200
+headers: {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
+body: '{"success": true, "token": "tXGuSQgOCVXcltkz"}'
+
+F
+/api/users/1000
+INFO     POST http://127.0.0.1:5000/api/users/1000
+ERROR    403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
+ERROR    validate: status_code equals 201(int)  ==> fail
+403(int) equals 201(int)
+ERROR    validate: content.success equals True(bool)    ==> fail
+False(bool) equals True(bool)
+ERROR    validate: content.msg equals user created successfully.(str)   ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
+====== request details ======
+url: http://127.0.0.1:5000/api/users/1000
+method: POST
+headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
+json: {'name': 'user1', 'password': '123456'}
+verify: True
+
+====== response details ======
+status_code: 403
+headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
+body: '{"success": false, "msg": "Authorization failed!"}'
+
+F
+
+======================================================================
+FAIL: test_0000_000 (httprunner.api.TestSequense)
+/api/get-token
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
+    test_runner.run_test(test_dict)
+httprunner.exceptions.ValidationFailure: validate: content.token equals baNLX1zhFYP11Seb(str)   ==> fail
+tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
+    self.fail(str(ex))
+AssertionError: validate: content.token equals baNLX1zhFYP11Seb(str)    ==> fail
+tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
+
+======================================================================
+FAIL: test_0001_000 (httprunner.api.TestSequense)
+/api/users/1000
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
+    test_runner.run_test(test_dict)
+httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int)  ==> fail
+403(int) equals 201(int)
+validate: content.success equals True(bool) ==> fail
+False(bool) equals True(bool)
+validate: content.msg equals user created successfully.(str)    ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
+    self.fail(str(ex))
+AssertionError: validate: status_code equals 201(int)   ==> fail
+403(int) equals 201(int)
+validate: content.success equals True(bool) ==> fail
+False(bool) equals True(bool)
+validate: content.msg equals user created successfully.(str)    ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.026s
+
+FAILED (failures=2)
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513835.html
+
+ + +

非常不幸,两个接口的测试用例均运行失败了。

+

优化测试用例

+

从两个测试步骤的报错信息和堆栈信息(Traceback)可以看出,第一个步骤失败的原因是获取的 token 与预期值不一致,第二个步骤失败的原因是请求权限校验失败(403)。

+

接下来我们将逐步进行进行优化。

+

调整校验器

+

默认情况下,har2case 生成用例时,若 HTTP 请求的响应内容为 JSON 格式,则会将第一层级中的所有key-value转换为 validator。

+

例如上面的第一个测试步骤,生成的 validator 为:

+
"validate": [
+    {"eq": ["status_code", 200]},
+    {"eq": ["headers.Content-Type", "application/json"]},
+    {"eq": ["content.success", true]},
+    {"eq": ["content.token", "baNLX1zhFYP11Seb"]}
+]
+
+ + +

运行测试用例时,就会对上面的各个项进行校验。

+

问题在于,请求/api/get-token接口时,每次生成的 token 都会是不同的,因此将生成的 token 作为校验项的话,校验自然就无法通过了。

+

正确的做法是,在测试步骤的 validate 中应该去掉这类动态变化的值。

+

去除该项后,将用例另存为 demo-quickstart-1.json(对应的 YAML 格式:demo-quickstart-1.yml)。

+

再次运行测试用例,运行结果如下:

+
$ hrun docs/data/demo-quickstart-1.yml
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 6.61 ms, response_length: 46 bytes
+
+.
+/api/users/1000
+INFO     POST http://127.0.0.1:5000/api/users/1000
+ERROR    403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
+ERROR    validate: status_code equals 201(int)  ==> fail
+403(int) equals 201(int)
+ERROR    validate: content.success equals True(bool)    ==> fail
+False(bool) equals True(bool)
+ERROR    validate: content.msg equals user created successfully.(str)   ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
+====== request details ======
+url: http://127.0.0.1:5000/api/users/1000
+method: POST
+headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
+json: {'name': 'user1', 'password': '123456'}
+verify: True
+
+====== response details ======
+status_code: 403
+headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:45:34 GMT'}
+body: '{"success": false, "msg": "Authorization failed!"}'
+
+F
+
+======================================================================
+FAIL: test_0001_000 (httprunner.api.TestSequense)
+/api/users/1000
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
+    test_runner.run_test(test_dict)
+httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int)  ==> fail
+403(int) equals 201(int)
+validate: content.success equals True(bool) ==> fail
+False(bool) equals True(bool)
+validate: content.msg equals user created successfully.(str)    ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
+    self.fail(str(ex))
+AssertionError: validate: status_code equals 201(int)   ==> fail
+403(int) equals 201(int)
+validate: content.success equals True(bool) ==> fail
+False(bool) equals True(bool)
+validate: content.msg equals user created successfully.(str)    ==> fail
+Authorization failed!(str) equals user created successfully.(str)
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.018s
+
+FAILED (failures=1)
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513934.html
+
+ + +

经过修改,第一个测试步骤已经运行成功了,第二个步骤仍然运行失败(403),还是因为权限校验的原因。

+

参数关联

+

我们继续查看 demo-quickstart-1.json,会发现第二个测试步骤的请求 headers 中的 token 仍然是硬编码的,即抓包时获取到的值。在我们再次运行测试用例时,这个 token 已经失效了,所以会出现 403 权限校验失败的问题。

+

正确的做法是,我们应该在每次运行测试用例的时候,先动态获取到第一个测试步骤中的 token,然后在后续测试步骤的请求中使用前面获取到的 token。

+

在 HttpRunner 中,支持参数提取(extract)和参数引用的功能($var)。

+

在测试步骤(test)中,若需要从响应结果中提取参数,则可使用 extract 关键字。extract 的列表中可指定一个或多个需要提取的参数。

+

在提取参数时,当 HTTP 的请求响应结果为 JSON 格式,则可以采用.运算符的方式,逐级往下获取到参数值;响应结果的整体内容引用方式为 content 或者 body。

+

例如,第一个接口/api/get-token的响应结果为:

+
{"success": true, "token": "ZQkYhbaQ6q8UFFNE"}
+
+ + +

那么要获取到 token 参数,就可以使用 content.token 的方式;具体的写法如下:

+
"extract": [
+  {"token": "content.token"}
+]
+
+ + +

其中,token 作为提取后的参数名称,可以在后续使用 $token 进行引用。

+
"headers": {
+  "device_sn": "FwgRiO7CNA50DSU",
+  "token": "$token",
+  "Content-Type": "application/json"
+}
+
+ + +

修改后的测试用例另存为 demo-quickstart-2.json(对应的 YAML 格式:demo-quickstart-2.yml)。

+

再次运行测试用例,运行结果如下:

+
$ hrun docs/data/demo-quickstart-2.yml
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 8.32 ms, response_length: 46 bytes
+
+.
+/api/users/1000
+INFO     POST http://127.0.0.1:5000/api/users/1000
+INFO     status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.019s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548514191.html
+
+ + +

经过修改,第二个测试步骤也运行成功了。

+

base_url

+

虽然测试步骤运行都成功了,但是仍然有继续优化的地方。

+

继续查看 demo-quickstart-2.json,我们会发现在每个测试步骤的 URL 中,都采用的是完整的描述(host+path),但大多数情况下同一个用例中的 host 都是相同的,区别仅在于 path 部分。

+

因此,我们可以将各个测试步骤(test) URL 的 base_url 抽取出来,放到全局配置模块(config)中,在测试步骤中的 URL 只保留 PATH 部分。

+
- config:
+    name: testcase description
+    base_url: http://127.0.0.1:5000
+
+- test:
+    name: get token
+    request:
+        url: /api/get-token
+
+ + +

调整后的测试用例另存为 demo-quickstart-3.json(对应的 YAML 格式:demo-quickstart-3.yml)。

+

重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。

+

变量的申明和引用

+

继续查看 demo-quickstart-3.json,我们会发现测试用例中存在较多硬编码的参数,例如 app_version、device_sn、os_platform、user_id 等。

+

大多数情况下,我们可以不用修改这些硬编码的参数,测试用例也能正常运行。但是为了更好地维护测试用例,例如同一个参数值在测试步骤中出现多次,那么比较好的做法是,将这些参数定义为变量,然后在需要参数的地方进行引用。

+

在 HttpRunner 中,支持变量申明(variables)和引用($var)的机制。在 config 和 test 中均可以通过 variables 关键字定义变量,然后在测试步骤中可以通过 $ + 变量名称 的方式引用变量。区别在于,在 config 中定义的变量为全局的,整个测试用例(testcase)的所有地方均可以引用;在 test 中定义的变量作用域仅局限于当前测试步骤(teststep)。

+

对上述各个测试步骤中硬编码的参数进行变量申明和引用调整后,新的测试用例另存为 demo-quickstart-4.json(对应的 YAML 格式:demo-quickstart-4.yml)。

+

重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。

+

抽取公共变量

+

查看 demo-quickstart-4.json 可以看出,两个测试步骤中都定义了 device_sn。针对这类公共的参数,我们可以将其统一定义在 config 的 variables 中,在测试步骤中就不用再重复定义。

+
- config:
+    name: testcase description
+    base_url: http://127.0.0.1:5000
+    variables:
+        device_sn: FwgRiO7CNA50DSU
+
+ + +

调整后的测试用例见 demo-quickstart-5.json(对应的 YAML 格式:demo-quickstart-5.yml)。

+

实现动态运算逻辑

+

demo-quickstart-5.yml 中,参数 device_sn 代表的是设备的 SN 编码,虽然采用硬编码的方式暂时不影响测试用例的运行,但这与真实的用户场景不大相符。

+

假设 device_sn 的格式为 15 长度的字符串,那么我们就可以在每次运行测试用例的时候,针对 device_sn 生成一个 15 位长度的随机字符串。与此同时,sign 字段是根据 headers 中的各个字段拼接后生成得到的 MD5 值,因此在 device_sn 变动后,sign 也应该重新进行计算,否则就会再次出现签名校验失败的问题。

+

然而,HttpRunner 的测试用例都是采用 YAML/JSON 格式进行描述的,在文本格式中如何执行代码运算呢?

+

HttpRunner 的实现方式为,支持热加载的插件机制(debugtalk.py),可以在 YAML/JSON 中调用 Python 函数。

+

具体地做法,我们可以在测试用例文件的同级或其父级目录中创建一个 debugtalk.py 文件,然后在其中定义相关的函数和变量。

+

例如,针对 device_sn 的随机字符串生成功能,我们可以定义一个 gen_random_string 函数;针对 sign 的签名算法,我们可以定义一个 get_sign 函数。

+
import hashlib
+import hmac
+import random
+import string
+
+SECRET_KEY = "DebugTalk"
+
+def gen_random_string(str_len):
+    random_char_list = []
+    for _ in range(str_len):
+        random_char = random.choice(string.ascii_letters + string.digits)
+        random_char_list.append(random_char)
+
+    random_string = ''.join(random_char_list)
+    return random_string
+
+def get_sign(*args):
+    content = ''.join(args).encode('ascii')
+    sign_key = SECRET_KEY.encode('ascii')
+    sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
+    return sign
+
+ + +

然后,我们在 YAML/JSON 测试用例文件中,就可以对定义的函数进行调用,对定义的变量进行引用了。引用变量的方式仍然与前面讲的一样,采用$ + 变量名称的方式;调用函数的方式为${func($var)}

+

例如,生成 15 位长度的随机字符串并赋值给 device_sn 的代码为:

+
"variables": [
+  {"device_sn": "${gen_random_string(15)}"}
+]
+
+ + +

使用 $user_agent、$device_sn、$os_platform、$app_version 根据签名算法生成 sign 值的代码为:

+
"json": {
+  "sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
+}
+
+ + +

对测试用例进行上述调整后,另存为 demo-quickstart-6.json(对应的 YAML 格式:demo-quickstart-6.yml)。

+

重启 flask 应用服务后再次运行测试用例,所有的测试步骤仍然运行成功。

+

参数化数据驱动

+
+

请确保你使用的 HttpRunner 版本号不低于 2.0.0

+
+

demo-quickstart-6.yml 中,user_id 仍然是写死的值,假如我们需要创建 user_id 为 1001~1004 的用户,那我们只能不断地去修改 user_id,然后运行测试用例,重复操作 4 次?或者我们在测试用例文件中将创建用户的 test 复制 4 份,然后在每一份里面分别使用不同的 user_id ?

+

很显然,不管是采用上述哪种方式,都会很繁琐,并且也无法应对灵活多变的测试需求。

+

针对这类需求,HttpRunner 支持参数化数据驱动的功能。

+

在 HttpRunner 中,若要采用数据驱动的方式来运行测试用例,需要创建一个文件,对测试用例进行引用,并使用 parameters 关键字定义参数并指定数据源取值方式。

+

例如,我们需要在创建用户的接口中对 user_id 进行参数化,参数化列表为 1001~1004,并且取值方式为顺序取值,那么最简单的描述方式就是直接指定参数列表。具体的编写方式为,新建一个测试场景文件 demo-quickstart-7.yml(对应的 JSON 格式:demo-quickstart-7.json),内容如下所示:

+
config:
+    name: testcase description
+
+testcases:
+    create user:
+        testcase: demo-quickstart-6.yml
+        parameters:
+            user_id: [1001, 1002, 1003, 1004]
+
+ + +

仅需如上配置,针对 user_id 的参数化数据驱动就完成了。

+

重启 flask 应用服务后再次运行测试用例,测试用例运行情况如下所示:

+
+点击查看运行日志 + +
$ hrun docs/data/demo-quickstart-7.json
+INFO     Start to run testcase: create user 1001
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes
+
+.
+/api/users/1001
+INFO     POST http://127.0.0.1:5000/api/users/1001
+INFO     status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.021s
+
+OK
+INFO     Start to run testcase: create user 1002
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes
+
+.
+/api/users/1002
+INFO     POST http://127.0.0.1:5000/api/users/1002
+INFO     status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.007s
+
+OK
+INFO     Start to run testcase: create user 1003
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes
+
+.
+/api/users/1003
+INFO     POST http://127.0.0.1:5000/api/users/1003
+INFO     status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.011s
+
+OK
+INFO     Start to run testcase: create user 1004
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes
+
+.
+/api/users/1004
+INFO     POST http://127.0.0.1:5000/api/users/1004
+INFO     status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.016s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html
+
+ + + +
+ +

可以看出,测试用例总共运行了 4 次,并且每次运行时都是采用的不同 user_id。

+

关于参数化数据驱动,这里只描述了最简单的场景和使用方式,如需了解更多,请进一步阅读《数据驱动使用手册》

+

查看测试报告

+

在每次使用 hrun 命令运行测试用例后,均会生成一份 HTML 格式的测试报告。报告文件位于 reports 目录下,文件名称为测试用例的开始运行时间。

+

例如,在运行完 demo-quickstart-1.json 后,将生成如下形式的测试报告:

+

+

+

+

关于测试报告的详细内容,请查看《测试报告》部分。

+

总结

+

到此为止,HttpRunner 的核心功能就介绍完了,掌握本文中的功能特性,足以帮助你应对日常项目工作中至少 80% 的自动化测试需求。

+

当然,HttpRunner 不止于此,如需挖掘 HttpRunner 的更多特性,实现更复杂场景的自动化测试需求,可继续阅读后续文档。

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/related-docs/index.html b/related-docs/index.html new file mode 100644 index 00000000..6c32f999 --- /dev/null +++ b/related-docs/index.html @@ -0,0 +1,811 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 相关资料 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/run-tests/cli/index.html b/run-tests/cli/index.html new file mode 100644 index 00000000..101c6149 --- /dev/null +++ b/run-tests/cli/index.html @@ -0,0 +1,1051 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 运行测试(CLI) - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

运行测试(CLI)

+ +

HttpRunner 在命令行中启动测试时,通过指定参数,可实现丰富的测试特性控制。

+
$ hrun -h
+usage: hrun [-h] [-V] [--log-level LOG_LEVEL] [--log-file LOG_FILE]
+            [--dot-env-path DOT_ENV_PATH] [--report-template REPORT_TEMPLATE]
+            [--report-dir REPORT_DIR] [--failfast] [--save-tests]
+            [--startproject STARTPROJECT]
+            [--validate [VALIDATE [VALIDATE ...]]]
+            [--prettify [PRETTIFY [PRETTIFY ...]]]
+            [testcase_paths [testcase_paths ...]]
+
+One-stop solution for HTTP(S) testing.
+
+positional arguments:
+  testcase_paths        testcase file path
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -V, --version         show version
+  --log-level LOG_LEVEL
+                        Specify logging level, default is INFO.
+  --log-file LOG_FILE   Write logs to specified file path.
+  --dot-env-path DOT_ENV_PATH
+                        Specify .env file path, which is useful for keeping
+                        sensitive data.
+  --report-template REPORT_TEMPLATE
+                        specify report template path.
+  --report-dir REPORT_DIR
+                        specify report save directory.
+  --failfast            Stop the test run on the first error or failure.
+  --save-tests          Save loaded tests and parsed tests to JSON file.
+  --startproject STARTPROJECT
+                        Specify new project name.
+  --validate [VALIDATE [VALIDATE ...]]
+                        Validate JSON testcase format.
+  --prettify [PRETTIFY [PRETTIFY ...]]
+                        Prettify JSON testcase format.
+
+ + +

指定测试用例路径

+

使用 HttpRunner 指定测试用例路径时,支持多种方式。

+

使用 hrun 命令外加单个测试用例文件的路径,运行单个测试用例,并生成一个测试报告文件:

+
$ hrun filepath/testcase.yml
+
+ + +

将多个测试用例文件放置到文件夹中,指定文件夹路径可将文件夹下所有测试用例作为测试用例集进行运行,并生成一个测试报告文件:

+
$ hrun testcases_folder_path
+
+ + +

failfast

+

默认情况下,HttpRunner 会运行指定用例集中的所有测试用例,并统计测试结果。

+
+

对于某些依赖于执行顺序的测试用例,例如需要先登录成功才能执行后续接口请求的场景,当前面的测试用例执行失败后,后续的测试用例也都必将失败,因此没有继续执行的必要了。

+
+

若希望测试用例在运行过程中,遇到失败时不再继续运行后续用例,则可通过在命令中添加--failfast实现。

+
$ hrun filepath/testcase.yml --failfast
+
+ + +

日志级别

+

默认情况下,HttpRunner 运行时的日志级别为INFO,只会包含最基本的信息,包括用例名称、请求的URL和Method、响应结果的状态码、耗时和内容大小。

+
$ hrun docs/data/demo-quickstart-6.json
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 9.08 ms, response_length: 46 bytes
+
+.
+/api/users/1548560655759
+INFO     POST http://127.0.0.1:5000/api/users/1548560655759
+INFO     status_code: 201, response_time(ms): 2.89 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.019s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560655.html
+
+ + +

若需要查看到更详尽的信息,例如请求的参数和响应的详细内容,可以将日志级别设置为DEBUG,即在命令中添加--log-level debug

+
$ hrun docs/data/demo-quickstart-6.json --log-level debug
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+DEBUG    request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True}
+DEBUG    processed request:
+> POST http://127.0.0.1:5000/api/get-token
+> kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True, 'timeout': 120}
+DEBUG
+================== request details ==================
+url              : 'http://127.0.0.1:5000/api/get-token'
+method           : 'POST'
+headers          : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json', 'Content-Length': '52'}
+body             : b'{"sign": "2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff"}'
+
+DEBUG
+================== response details ==================
+ok               : True
+url              : 'http://127.0.0.1:5000/api/get-token'
+status_code      : 200
+reason           : 'OK'
+cookies          : {}
+encoding         : None
+headers          : {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'}
+content_type     : 'application/json'
+json             : {'success': True, 'token': 'o6uakmubLrCbpRRS'}
+
+INFO     status_code: 200, response_time(ms): 9.28 ms, response_length: 46 bytes
+
+DEBUG    start to extract from response object.
+DEBUG    extract: content.token => o6uakmubLrCbpRRS
+DEBUG    start to validate.
+DEBUG    extract: status_code   => 200
+DEBUG    validate: status_code equals 200(int)  ==> pass
+DEBUG    extract: headers.Content-Type  => application/json
+DEBUG    validate: headers.Content-Type equals application/json(str)    ==> pass
+DEBUG    extract: content.success   => True
+DEBUG    validate: content.success equals True(bool)    ==> pass
+.
+/api/users/1548560716736
+INFO     POST http://127.0.0.1:5000/api/users/1548560716736
+DEBUG    request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True}
+DEBUG    processed request:
+> POST http://127.0.0.1:5000/api/users/1548560716736
+> kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True, 'timeout': 120}
+DEBUG
+================== request details ==================
+url              : 'http://127.0.0.1:5000/api/users/1548560716736'
+method           : 'POST'
+headers          : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json', 'Content-Length': '39'}
+body             : b'{"name": "user1", "password": "123456"}'
+
+DEBUG
+================== response details ==================
+ok               : True
+url              : 'http://127.0.0.1:5000/api/users/1548560716736'
+status_code      : 201
+reason           : 'CREATED'
+cookies          : {}
+encoding         : None
+headers          : {'Content-Type': 'application/json', 'Content-Length': '54', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'}
+content_type     : 'application/json'
+json             : {'success': True, 'msg': 'user created successfully.'}
+
+INFO     status_code: 201, response_time(ms): 2.77 ms, response_length: 54 bytes
+
+DEBUG    start to validate.
+DEBUG    extract: status_code   => 201
+DEBUG    validate: status_code equals 201(int)  ==> pass
+DEBUG    extract: headers.Content-Type  => application/json
+DEBUG    validate: headers.Content-Type equals application/json(str)    ==> pass
+DEBUG    extract: content.success   => True
+DEBUG    validate: content.success equals True(bool)    ==> pass
+DEBUG    extract: content.msg   => user created successfully.
+DEBUG    validate: content.msg equals user created successfully.(str)   ==> pass
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.022s
+
+OK
+DEBUG    No html report template specified, use default.
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560716.html
+
+ + +

保存详细过程数据

+

为了方便定位问题,运行测试时可指定 --save-tests 参数,即可将运行过程的中间数据保存为日志文件。

+
$ hrun docs/data/demo-quickstart-6.json --save-tests
+dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.loaded.json
+dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.parsed.json
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 11.42 ms, response_length: 46 bytes
+
+.
+/api/users/1548560768589
+INFO     POST http://127.0.0.1:5000/api/users/1548560768589
+INFO     status_code: 201, response_time(ms): 2.8 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.028s
+
+OK
+dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.summary.json
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560768.html
+
+ + +

日志文件将保存在项目根目录的 logs 文件夹中,生成的文件有如下三个(XXX为测试用例名称):

+
    +
  • XXX.loaded.json:测试用例加载后的数据结构内容,加载包括测试用例文件(YAML/JSON)、debugtalk.py、.env 等所有项目文件,例如 demo-quickstart-6.loaded.json
  • +
  • XXX.parsed.json:测试用例解析后的数据结构内容,解析内容包括测试用例引用(API/testcase)、变量计算和替换、base_url 拼接等,例如 demo-quickstart-6.parsed.json
  • +
  • XXX.summary.json:测试报告生成前的数据结构内容,例如 demo-quickstart-6.summary.json
  • +
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/run-tests/load-test/index.html b/run-tests/load-test/index.html new file mode 100644 index 00000000..37a9b4b9 --- /dev/null +++ b/run-tests/load-test/index.html @@ -0,0 +1,981 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 性能测试 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

性能测试

+ +

HttpRunner 通过复用 Locust,可以在无需对 YAML/JSON 进行任何修改的情况下,直接运行性能测试。

+

原理图

+

+

安装依赖包

+

安装完成 HttpRunner 后,系统中会新增locusts命令,但不会同时安装 Locust。

+

在系统中未安装 Locust 的情况下,运行locusts命令时会出现如下提示。

+
$ locusts -V
+WARNING  Locust is not installed, install first and try again.
+install command: pip install locustio
+
+ + +

Locust 的安装方式如下:

+
$ pip install locustio
+
+ + +

安装完成后,执行 locusts -V 可查看到 Locust 的版本号。

+
$ locusts -V
+[2017-08-26 23:45:42,246] bogon/INFO/stdout: Locust 0.8a2
+[2017-08-26 23:45:42,246] bogon/INFO/stdout:
+
+ + +

执行 locusts -h,可查看到使用帮助文档。

+
$ locusts -h
+Usage: locust [options] [LocustClass [LocustClass2 ... ]]
+
+Options:
+  -h, --help            show this help message and exit
+  -H HOST, --host=HOST  Host to load test in the following format:
+                        http://10.21.32.33
+  --web-host=WEB_HOST   Host to bind the web interface to. Defaults to '' (all
+                        interfaces)
+  -P PORT, --port=PORT, --web-port=PORT
+                        Port on which to run web host
+  -f LOCUSTFILE, --locustfile=LOCUSTFILE
+                        Python module file to import, e.g. '../other.py'.
+                        Default: locustfile
+  --csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE
+                        Store current request stats to files in CSV format.
+  --master              Set locust to run in distributed mode with this
+                        process as master
+  --slave               Set locust to run in distributed mode with this
+                        process as slave
+  --master-host=MASTER_HOST
+                        Host or IP address of locust master for distributed
+                        load testing. Only used when running with --slave.
+                        Defaults to 127.0.0.1.
+  --master-port=MASTER_PORT
+                        The port to connect to that is used by the locust
+                        master for distributed load testing. Only used when
+                        running with --slave. Defaults to 5557. Note that
+                        slaves will also connect to the master node on this
+                        port + 1.
+  --master-bind-host=MASTER_BIND_HOST
+                        Interfaces (hostname, ip) that locust master should
+                        bind to. Only used when running with --master.
+                        Defaults to * (all available interfaces).
+  --master-bind-port=MASTER_BIND_PORT
+                        Port that locust master should bind to. Only used when
+                        running with --master. Defaults to 5557. Note that
+                        Locust will also use this port + 1, so by default the
+                        master node will bind to 5557 and 5558.
+  --expect-slaves=EXPECT_SLAVES
+                        How many slaves master should expect to connect before
+                        starting the test (only when --no-web used).
+  --no-web              Disable the web interface, and instead start running
+                        the test immediately. Requires -c and -r to be
+                        specified.
+  -c NUM_CLIENTS, --clients=NUM_CLIENTS
+                        Number of concurrent clients. Only used together with
+                        --no-web
+  -r HATCH_RATE, --hatch-rate=HATCH_RATE
+                        The rate per second in which clients are spawned. Only
+                        used together with --no-web
+  -n NUM_REQUESTS, --num-request=NUM_REQUESTS
+                        Number of requests to perform. Only used together with
+                        --no-web
+  -L LOGLEVEL, --loglevel=LOGLEVEL
+                        Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.
+                        Default is INFO.
+  --logfile=LOGFILE     Path to log file. If not set, log will go to
+                        stdout/stderr
+  --print-stats         Print stats in the console
+  --only-summary        Only print the summary stats
+  --no-reset-stats      Do not reset statistics once hatching has been
+                        completed
+  -l, --list            Show list of possible locust classes and exit
+  --show-task-ratio     print table of the locust classes' task execution
+                        ratio
+  --show-task-ratio-json
+                        print json data of the locust classes' task execution
+                        ratio
+  -V, --version         show program's version number and exit
+
+ + +

可以看出,loucsts 命令与 locust 命令的用法基本相同。

+

相比于 locust 命令,loucsts命令主要存在如下两项差异。

+

运行性能测试

+

-f 参数后面,loucsts 命令不仅可以指定 Locust 支持的 Python 文件,同时可以直接指定 YAML/JSON 格式的测试用例文件。在具体实现上,当 -f 指定 YAML/JSON 格式的测试用例文件时,会先将其转换为 Python 格式的 locustfile,然后再将 locustfile.py 传给 locust 命令。

+
$ locusts -f examples/first-testcase.yml
+[2017-08-18 17:20:43,915] Leos-MacBook-Air.local/INFO/locust.main: Starting web monitor at *:8089
+[2017-08-18 17:20:43,918] Leos-MacBook-Air.local/INFO/locust.main: Starting Locust 0.8a2
+
+ + +

执行上述命令后,即完成了 Locust 服务的启动,后续就可以在 Locust 的 Web 管理界面中进行操作了,使用方式与 Locust 完全相同。

+

多进程运行模式

+

默认情况下,在 Locust 中如需使用 master-slave 模式启动多个进程(使用多核处理器的能力),只能先启动 master,然后再逐一启动若干个 slave。

+
$ locust -f locustfile.py --master
+$ locust -f locustfile.py --slave &
+$ locust -f locustfile.py --slave &
+$ locust -f locustfile.py --slave &
+$ locust -f locustfile.py --slave &
+
+ + +

在 HttpRunner 中,新增实现 --processes 参数,可以一次性启动 1 个 master 和多个 salve。若在 --processes 参数后没有指定具体的数值,则启动的 slave 个数与机器的 CPU 核数相同。

+
$ locusts -f examples/first-testcase.yml --processes 4
+[2017-08-26 23:51:47,071] bogon/INFO/locust.main: Starting web monitor at *:8089
+[2017-08-26 23:51:47,075] bogon/INFO/locust.main: Starting Locust 0.8a2
+[2017-08-26 23:51:47,078] bogon/INFO/locust.main: Starting Locust 0.8a2
+[2017-08-26 23:51:47,080] bogon/INFO/locust.main: Starting Locust 0.8a2
+[2017-08-26 23:51:47,083] bogon/INFO/locust.main: Starting Locust 0.8a2
+[2017-08-26 23:51:47,084] bogon/INFO/locust.runners: Client 'bogon_656e0af8e968a8533d379dd252422ad3' reported as ready. Currently 1 clients ready to swarm.
+[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_09f73850252ee4ec739ed77d3c4c6dba' reported as ready. Currently 2 clients ready to swarm.
+[2017-08-26 23:51:47,084] bogon/INFO/locust.main: Starting Locust 0.8a2
+[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_869f7ed671b1a9952b56610f01e2006f' reported as ready. Currently 3 clients ready to swarm.
+[2017-08-26 23:51:47,085] bogon/INFO/locust.runners: Client 'bogon_80a804cda36b80fac17b57fd2d5e7cdb' reported as ready. Currently 4 clients ready to swarm.
+
+ + +

+

Enjoy!

+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/run-tests/report/index.html b/run-tests/report/index.html new file mode 100644 index 00000000..54f27f77 --- /dev/null +++ b/run-tests/report/index.html @@ -0,0 +1,967 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 测试报告 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

测试报告

+ +

使用 HttpRunner 执行完自动化测试后,会在当前路径的 reports 目录下生成一份 HTML 格式的测试报告。

+

默认情况

+

默认情况下,生成的测试报告文件会位于项目根目录的 reports 文件夹中,文件名称为测试开始的时间戳。

+
$ hrun docs/data/demo-quickstart-6.yml
+INFO     Start to run testcase: testcase description
+/api/get-token
+INFO     POST http://127.0.0.1:5000/api/get-token
+INFO     status_code: 200, response_time(ms): 10.05 ms, response_length: 46 bytes
+
+.
+/api/users/1548561170497
+INFO     POST http://127.0.0.1:5000/api/users/1548561170497
+INFO     status_code: 201, response_time(ms): 2.88 ms, response_length: 54 bytes
+
+.
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.034s
+
+OK
+INFO     Start to render Html report ...
+INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548561170.html
+
+ + +

点击查看测试报告

+

默认报告样式

+

在 HttpRunner 中自带了一个 Jinja2 格式的报告模版,默认情况下,生成的报告样式均基于该模版(httprunner/templates/default_report_template.html)。

+

测试报告形式如下:

+

在 Summary 中,会罗列本次测试的整体信息,包括测试开始时间、总运行时长、运行的Python版本和系统环境、运行结果统计数据。

+

+

在 Details 中,会详细展示每一条测试用例的运行结果。

+

点击测试用例对应的 log 按钮,会在弹出框中展示该用例执行的详细数据,包括请求的 headers 和 body、响应的 headers 和 body、校验结果、响应、响应耗时(elapsed)等信息。

+

+

+

若测试用例运行不成功(failed/error/skipped),则在该测试用例的 detail 中会出现 traceback 按钮,点击该按钮后,会在弹出框中展示失败的堆栈日志,或者 skipped 的原因。

+

+

点击查看测试报告

+

自定义

+

除了默认的报告样式,HttpRunner 还支持使用自定义的报告模板。

+

编写自定义模板(Jinja2格式)

+

自定义模板需要采用 Jinja2 的格式,其中可以使用的数据可参考数据结构示例

+

例如,我们需要在自定义模板中展示测试结果的统计数据,就可以采用如下方式进行描述:

+
<tr>
+  <th>TOTAL</th>
+  <th>SUCCESS</th>
+  <th>FAILED</th>
+  <th>ERROR</th>
+  <th>SKIPPED</th>
+</tr>
+<tr>
+  <td>{{stat.testsRun}}</td>
+  <td>{{stat.successes}}</td>
+  <td>{{stat.failures}}</td>
+  <td>{{stat.errors}}</td>
+  <td>{{stat.skipped}}</td>
+</tr>
+
+ + +

在自定义报告模板时,可以参考 HttpRunner 的默认报告模板,要实现更复杂的模版功能,可参考 Jinja2 的使用文档。

+

使用自定义模板

+

使用自定义模版时,需要通过 --report-template 指定报告模板的路径,然后测试运行完成后,就会采用自定义的模板生成测试报告。

+
$ hrun docs/data/demo-quickstart-2.yml --report-template /path/to/custom_report_template
+...
+同上,省略
+
+INFO     render with html report template: /path/to/custom_report_template
+INFO     Start to render Html report ...
+INFO     Generated Html report: reports/1532078874.html
+
+ + +

指定报告生成路径

+

默认情况下,生成的测试报告文件会位于项目根目录的 reports 文件夹中。如需指定生成报告的路径,可以使用 --report-dir 参数。

+
$ hrun docs/data/demo-quickstart-2.yml --dirreport-name /other/path/
+...
+同上,省略
+
+INFO     Start to render Html report ...
+INFO     Generated Html report: /other/path/1532078874.html
+
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..6f809cf6 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"HttpRunner \u662f\u4e00\u6b3e\u9762\u5411 HTTP(S) \u534f\u8bae\u7684\u901a\u7528\u6d4b\u8bd5\u6846\u67b6\uff0c\u53ea\u9700\u7f16\u5199\u7ef4\u62a4\u4e00\u4efd YAML/JSON \u811a\u672c\uff0c\u5373\u53ef\u5b9e\u73b0\u81ea\u52a8\u5316\u6d4b\u8bd5\u3001\u6027\u80fd\u6d4b\u8bd5\u3001\u7ebf\u4e0a\u76d1\u63a7\u3001\u6301\u7eed\u96c6\u6210\u7b49\u591a\u79cd\u6d4b\u8bd5\u9700\u6c42\u3002 \u6b64\u6587\u6863\u9002\u7528\u4e8e\u5168\u65b0\u53d1\u5e03\u7684 HttpRunner 2.x \u7248\u672c\uff0c 1.x \u7248\u672c\u7684\u4f7f\u7528\u6587\u6863\u8bf7\u67e5\u770b \u5386\u53f2\u94fe\u63a5 \u3002 \u8bbe\u8ba1\u7406\u5ff5 \u00b6 \u5145\u5206\u590d\u7528\u4f18\u79c0\u7684\u5f00\u6e90\u9879\u76ee\uff0c\u4e0d\u8ffd\u6c42\u91cd\u590d\u9020\u8f6e\u5b50\uff0c\u800c\u662f\u5c06\u5f3a\u5927\u7684\u8f6e\u5b50\u7ec4\u88c5\u6210\u6218\u8f66 \u9075\u5faa \u7ea6\u5b9a\u5927\u4e8e\u914d\u7f6e \u7684\u51c6\u5219\uff0c\u5728\u6846\u67b6\u529f\u80fd\u4e2d\u878d\u5165\u81ea\u52a8\u5316\u6d4b\u8bd5\u6700\u4f73\u5de5\u7a0b\u5b9e\u8df5 \u8ffd\u6c42\u6295\u5165\u4ea7\u51fa\u6bd4\uff0c\u4e00\u4efd\u6295\u5165\u5373\u53ef\u5b9e\u73b0\u591a\u79cd\u6d4b\u8bd5\u9700\u6c42 \u6838\u5fc3\u7279\u6027 \u00b6 \u7ee7\u627f Requests \u7684\u5168\u90e8\u7279\u6027\uff0c\u8f7b\u677e\u5b9e\u73b0 HTTP(S) \u7684\u5404\u79cd\u6d4b\u8bd5\u9700\u6c42 \u91c7\u7528 YAML/JSON \u7684\u5f62\u5f0f\u63cf\u8ff0\u6d4b\u8bd5\u573a\u666f\uff0c\u4fdd\u969c\u6d4b\u8bd5\u7528\u4f8b\u63cf\u8ff0\u7684\u7edf\u4e00\u6027\u548c\u53ef\u7ef4\u62a4\u6027 \u501f\u52a9\u8f85\u52a9\u51fd\u6570\uff08debugtalk.py\uff09\uff0c\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\u8f7b\u677e\u5b9e\u73b0\u590d\u6742\u7684\u52a8\u6001\u8ba1\u7b97\u903b\u8f91 \u652f\u6301\u5b8c\u5584\u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\uff0c\u5145\u5206\u5b9e\u73b0\u6d4b\u8bd5\u7528\u4f8b\u7684\u590d\u7528 \u6d4b\u8bd5\u524d\u540e\u652f\u6301\u5b8c\u5584\u7684 hook \u673a\u5236 \u54cd\u5e94\u7ed3\u679c\u652f\u6301\u4e30\u5bcc\u7684\u6821\u9a8c\u673a\u5236 \u57fa\u4e8e HAR \u5b9e\u73b0\u63a5\u53e3\u5f55\u5236\u548c\u7528\u4f8b\u751f\u6210\u529f\u80fd\uff08 har2case \uff09 \u7ed3\u5408 Locust \u6846\u67b6\uff0c\u65e0\u9700\u989d\u5916\u7684\u5de5\u4f5c\u5373\u53ef\u5b9e\u73b0\u5206\u5e03\u5f0f\u6027\u80fd\u6d4b\u8bd5 \u6267\u884c\u65b9\u5f0f\u91c7\u7528 CLI \u8c03\u7528\uff0c\u53ef\u4e0e Jenkins \u7b49\u6301\u7eed\u96c6\u6210\u5de5\u5177\u5b8c\u7f8e\u7ed3\u5408 \u6d4b\u8bd5\u7ed3\u679c\u7edf\u8ba1\u62a5\u544a\u7b80\u6d01\u6e05\u6670\uff0c\u9644\u5e26\u8be6\u5c3d\u7edf\u8ba1\u4fe1\u606f\u548c\u65e5\u5fd7\u8bb0\u5f55 \u6781\u5f3a\u7684\u53ef\u6269\u5c55\u6027\uff0c\u8f7b\u677e\u5b9e\u73b0\u4e8c\u6b21\u5f00\u53d1\u548c Web \u5e73\u53f0\u5316 \u66f4\u591a\u4fe1\u606f \u00b6 \u5173\u6ce8 HttpRunner \u7684\u5fae\u4fe1\u516c\u4f17\u53f7\uff0c\u7b2c\u4e00\u65f6\u95f4\u83b7\u5f97\u6700\u65b0\u8d44\u8baf\u3002","title":"\u4ecb\u7ecd"},{"location":"#_1","text":"\u5145\u5206\u590d\u7528\u4f18\u79c0\u7684\u5f00\u6e90\u9879\u76ee\uff0c\u4e0d\u8ffd\u6c42\u91cd\u590d\u9020\u8f6e\u5b50\uff0c\u800c\u662f\u5c06\u5f3a\u5927\u7684\u8f6e\u5b50\u7ec4\u88c5\u6210\u6218\u8f66 \u9075\u5faa \u7ea6\u5b9a\u5927\u4e8e\u914d\u7f6e \u7684\u51c6\u5219\uff0c\u5728\u6846\u67b6\u529f\u80fd\u4e2d\u878d\u5165\u81ea\u52a8\u5316\u6d4b\u8bd5\u6700\u4f73\u5de5\u7a0b\u5b9e\u8df5 \u8ffd\u6c42\u6295\u5165\u4ea7\u51fa\u6bd4\uff0c\u4e00\u4efd\u6295\u5165\u5373\u53ef\u5b9e\u73b0\u591a\u79cd\u6d4b\u8bd5\u9700\u6c42","title":"\u8bbe\u8ba1\u7406\u5ff5"},{"location":"#_2","text":"\u7ee7\u627f Requests \u7684\u5168\u90e8\u7279\u6027\uff0c\u8f7b\u677e\u5b9e\u73b0 HTTP(S) \u7684\u5404\u79cd\u6d4b\u8bd5\u9700\u6c42 \u91c7\u7528 YAML/JSON \u7684\u5f62\u5f0f\u63cf\u8ff0\u6d4b\u8bd5\u573a\u666f\uff0c\u4fdd\u969c\u6d4b\u8bd5\u7528\u4f8b\u63cf\u8ff0\u7684\u7edf\u4e00\u6027\u548c\u53ef\u7ef4\u62a4\u6027 \u501f\u52a9\u8f85\u52a9\u51fd\u6570\uff08debugtalk.py\uff09\uff0c\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\u8f7b\u677e\u5b9e\u73b0\u590d\u6742\u7684\u52a8\u6001\u8ba1\u7b97\u903b\u8f91 \u652f\u6301\u5b8c\u5584\u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\uff0c\u5145\u5206\u5b9e\u73b0\u6d4b\u8bd5\u7528\u4f8b\u7684\u590d\u7528 \u6d4b\u8bd5\u524d\u540e\u652f\u6301\u5b8c\u5584\u7684 hook \u673a\u5236 \u54cd\u5e94\u7ed3\u679c\u652f\u6301\u4e30\u5bcc\u7684\u6821\u9a8c\u673a\u5236 \u57fa\u4e8e HAR \u5b9e\u73b0\u63a5\u53e3\u5f55\u5236\u548c\u7528\u4f8b\u751f\u6210\u529f\u80fd\uff08 har2case \uff09 \u7ed3\u5408 Locust \u6846\u67b6\uff0c\u65e0\u9700\u989d\u5916\u7684\u5de5\u4f5c\u5373\u53ef\u5b9e\u73b0\u5206\u5e03\u5f0f\u6027\u80fd\u6d4b\u8bd5 \u6267\u884c\u65b9\u5f0f\u91c7\u7528 CLI \u8c03\u7528\uff0c\u53ef\u4e0e Jenkins \u7b49\u6301\u7eed\u96c6\u6210\u5de5\u5177\u5b8c\u7f8e\u7ed3\u5408 \u6d4b\u8bd5\u7ed3\u679c\u7edf\u8ba1\u62a5\u544a\u7b80\u6d01\u6e05\u6670\uff0c\u9644\u5e26\u8be6\u5c3d\u7edf\u8ba1\u4fe1\u606f\u548c\u65e5\u5fd7\u8bb0\u5f55 \u6781\u5f3a\u7684\u53ef\u6269\u5c55\u6027\uff0c\u8f7b\u677e\u5b9e\u73b0\u4e8c\u6b21\u5f00\u53d1\u548c Web \u5e73\u53f0\u5316","title":"\u6838\u5fc3\u7279\u6027"},{"location":"#_3","text":"\u5173\u6ce8 HttpRunner \u7684\u5fae\u4fe1\u516c\u4f17\u53f7\uff0c\u7b2c\u4e00\u65f6\u95f4\u83b7\u5f97\u6700\u65b0\u8d44\u8baf\u3002","title":"\u66f4\u591a\u4fe1\u606f"},{"location":"FAQ/","text":"\u5e38\u89c1\u95ee\u9898 \u00b6","title":"FAQ"},{"location":"FAQ/#_1","text":"","title":"\u5e38\u89c1\u95ee\u9898"},{"location":"Installation/","text":"\u8fd0\u884c\u73af\u5883 \u00b6 HttpRunner \u662f\u4e00\u4e2a\u57fa\u4e8e Python \u5f00\u53d1\u7684\u6d4b\u8bd5\u6846\u67b6\uff0c\u53ef\u4ee5\u8fd0\u884c\u5728 macOS\u3001Linux\u3001Windows \u7cfb\u7edf\u5e73\u53f0\u4e0a\u3002 Python \u7248\u672c \uff1aHttpRunner \u652f\u6301 Python 3.4 \u53ca\u4ee5\u4e0a\u7684\u6240\u6709\u7248\u672c\uff0c\u5e76\u4f7f\u7528 Travis-CI \u8fdb\u884c\u4e86 \u6301\u7eed\u96c6\u6210\u6d4b\u8bd5 \uff0c\u6d4b\u8bd5\u8986\u76d6\u7684\u7248\u672c\u5305\u62ec 2.7/3.4/3.5/3.6/3.7\u3002\u867d\u7136 HttpRunner \u6682\u65f6\u4fdd\u7559\u4e86\u5bf9 Python 2.7 \u7684\u517c\u5bb9\u652f\u6301\uff0c\u4f46\u5f3a\u70c8\u5efa\u8bae\u4f7f\u7528 Python 3.4 \u53ca\u4ee5\u4e0a\u7248\u672c\u3002 \u64cd\u4f5c\u7cfb\u7edf \uff1a\u63a8\u8350\u4f7f\u7528 macOS/Linux\u3002 \u5b89\u88c5\u65b9\u5f0f \u00b6 HttpRunner \u7684\u7a33\u5b9a\u7248\u672c\u6258\u7ba1\u5728 PyPI \u4e0a\uff0c\u53ef\u4ee5\u4f7f\u7528 pip \u8fdb\u884c\u5b89\u88c5\u3002 $ pip install httprunner \u5982\u679c\u4f60\u9700\u8981\u4f7f\u7528\u6700\u65b0\u7684\u5f00\u53d1\u7248\u672c\uff0c\u90a3\u4e48\u53ef\u4ee5\u91c7\u7528\u9879\u76ee\u7684 GitHub \u4ed3\u5e93\u5730\u5740\u8fdb\u884c\u5b89\u88c5\uff1a $ pip install git+https://github.com/HttpRunner/HttpRunner.git@master \u7248\u672c\u5347\u7ea7 \u00b6 \u5047\u5982\u4f60\u4e4b\u524d\u5df2\u7ecf\u5b89\u88c5\u8fc7\u4e86 HttpRunner\uff0c\u73b0\u5728\u9700\u8981\u5347\u7ea7\u5230\u6700\u65b0\u7248\u672c\uff0c\u90a3\u4e48\u4f60\u53ef\u4ee5\u4f7f\u7528 -U \u53c2\u6570\u3002\u8be5\u53c2\u6570\u5bf9\u4ee5\u4e0a\u4e09\u79cd\u5b89\u88c5\u65b9\u5f0f\u5747\u751f\u6548\u3002 $ pip install -U HttpRunner $ pip install -U git+https://github.com/HttpRunner/HttpRunner.git@master \u5b89\u88c5\u6821\u9a8c \u00b6 \u5728 HttpRunner \u5b89\u88c5\u6210\u529f\u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e\u5982\u4e0b 5 \u4e2a\u547d\u4ee4\uff1a httprunner : \u6838\u5fc3\u547d\u4ee4 ate : \u66fe\u7ecf\u7528\u8fc7\u7684\u547d\u4ee4\uff08\u5f53\u65f6\u6846\u67b6\u540d\u79f0\u4e3a ApiTestEngine\uff09\uff0c\u529f\u80fd\u4e0e httprunner \u5b8c\u5168\u76f8\u540c hrun : httprunner \u7684\u7f29\u5199\uff0c\u529f\u80fd\u4e0e httprunner \u5b8c\u5168\u76f8\u540c locusts : \u57fa\u4e8e Locust \u5b9e\u73b0 \u6027\u80fd\u6d4b\u8bd5 har2case : \u8f85\u52a9\u5de5\u5177\uff0c\u53ef\u5c06\u6807\u51c6\u901a\u7528\u7684 HAR \u683c\u5f0f\uff08HTTP Archive\uff09\u8f6c\u6362\u4e3a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b httprunner\u3001hrun\u3001ate \u4e09\u4e2a\u547d\u4ee4\u5b8c\u5168\u7b49\u4ef7\uff0c\u529f\u80fd\u7279\u6027\u5b8c\u5168\u76f8\u540c\uff0c\u4e2a\u4eba\u63a8\u8350\u4f7f\u7528 hrun \u547d\u4ee4\u3002 \u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff0c\u82e5\u6b63\u5e38\u663e\u793a\u7248\u672c\u53f7\uff0c\u5219\u8bf4\u660e HttpRunner \u5b89\u88c5\u6210\u529f\u3002 $ hrun -V 2.0.2 $ har2case -V 0.2.0 \u5f00\u53d1\u8005\u6a21\u5f0f \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5b89\u88c5 HttpRunner \u7684\u65f6\u5019\u53ea\u4f1a\u5b89\u88c5\u8fd0\u884c HttpRunner \u7684\u5fc5\u8981\u4f9d\u8d56\u5e93\u3002 \u5982\u679c\u4f60\u4e0d\u4ec5\u4ec5\u662f\u4f7f\u7528 HttpRunner\uff0c\u8fd8\u9700\u8981\u5bf9 HttpRunner \u8fdb\u884c\u5f00\u53d1\u8c03\u8bd5\uff08debug\uff09\uff0c\u90a3\u4e48\u5c31\u9700\u8981\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\u3002 HttpRunner \u4f7f\u7528 pipenv \u5bf9\u4f9d\u8d56\u5305\u8fdb\u884c\u7ba1\u7406\uff0c\u82e5\u4f60\u8fd8\u6ca1\u6709\u5b89\u88c5 pipenv\uff0c\u9700\u8981\u5148\u6267\u884c\u5982\u4e0b\u547d\u4ee4\u8fdb\u884c\u6309\u7167\uff1a $ pip install pipenv \u83b7\u53d6 HttpRunner \u6e90\u7801\uff1a $ git clone https://github.com/HttpRunner/HttpRunner.git \u8fdb\u5165\u4ed3\u5e93\u76ee\u5f55\uff0c\u5b89\u88c5\u6240\u6709\u4f9d\u8d56\uff1a $ pipenv install --dev \u8fd0\u884c\u5355\u5143\u6d4b\u8bd5\uff0c\u82e5\u6d4b\u8bd5\u5168\u90e8\u901a\u8fc7\uff0c\u5219\u8bf4\u660e\u73af\u5883\u6b63\u5e38\u3002 $ pipenv run python -m unittest discover \u67e5\u770b HttpRunner \u7684\u4f9d\u8d56\u60c5\u51b5\uff1a $ pipenv graph HttpRunner==2.0.0 - colorama [required: Any, installed: 0.4.0] - colorlog [required: Any, installed: 3.1.4] - har2case [required: Any, installed: 0.2.0] - PyYAML [required: Any, installed: 3.13] - Jinja2 [required: Any, installed: 2.10] - MarkupSafe [required: >=0.23, installed: 1.0] - PyYAML [required: Any, installed: 3.13] - requests [required: Any, installed: 2.20.0] - certifi [required: >=2017.4.17, installed: 2018.10.15] - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4] - idna [required: >=2.5,<2.8, installed: 2.7] - urllib3 [required: >=1.21.1,<1.25, installed: 1.24] - requests-toolbelt [required: Any, installed: 0.8.0] - requests [required: >=2.0.1,<3.0.0, installed: 2.20.0] - certifi [required: >=2017.4.17, installed: 2018.10.15] - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4] - idna [required: >=2.5,<2.8, installed: 2.7] - urllib3 [required: >=1.21.1,<1.25, installed: 1.24] \u8c03\u8bd5\u8fd0\u884c\u65b9\u5f0f\uff1a # \u8c03\u8bd5\u8fd0\u884c hrun $ pipenv run python main-debug.py hrun -h # \u8c03\u8bd5\u8fd0\u884c locusts $ pipenv run python main-debug.py locusts -h Docker \u00b6 TODO","title":"\u5b89\u88c5\u8bf4\u660e"},{"location":"Installation/#_1","text":"HttpRunner \u662f\u4e00\u4e2a\u57fa\u4e8e Python \u5f00\u53d1\u7684\u6d4b\u8bd5\u6846\u67b6\uff0c\u53ef\u4ee5\u8fd0\u884c\u5728 macOS\u3001Linux\u3001Windows \u7cfb\u7edf\u5e73\u53f0\u4e0a\u3002 Python \u7248\u672c \uff1aHttpRunner \u652f\u6301 Python 3.4 \u53ca\u4ee5\u4e0a\u7684\u6240\u6709\u7248\u672c\uff0c\u5e76\u4f7f\u7528 Travis-CI \u8fdb\u884c\u4e86 \u6301\u7eed\u96c6\u6210\u6d4b\u8bd5 \uff0c\u6d4b\u8bd5\u8986\u76d6\u7684\u7248\u672c\u5305\u62ec 2.7/3.4/3.5/3.6/3.7\u3002\u867d\u7136 HttpRunner \u6682\u65f6\u4fdd\u7559\u4e86\u5bf9 Python 2.7 \u7684\u517c\u5bb9\u652f\u6301\uff0c\u4f46\u5f3a\u70c8\u5efa\u8bae\u4f7f\u7528 Python 3.4 \u53ca\u4ee5\u4e0a\u7248\u672c\u3002 \u64cd\u4f5c\u7cfb\u7edf \uff1a\u63a8\u8350\u4f7f\u7528 macOS/Linux\u3002","title":"\u8fd0\u884c\u73af\u5883"},{"location":"Installation/#_2","text":"HttpRunner \u7684\u7a33\u5b9a\u7248\u672c\u6258\u7ba1\u5728 PyPI \u4e0a\uff0c\u53ef\u4ee5\u4f7f\u7528 pip \u8fdb\u884c\u5b89\u88c5\u3002 $ pip install httprunner \u5982\u679c\u4f60\u9700\u8981\u4f7f\u7528\u6700\u65b0\u7684\u5f00\u53d1\u7248\u672c\uff0c\u90a3\u4e48\u53ef\u4ee5\u91c7\u7528\u9879\u76ee\u7684 GitHub \u4ed3\u5e93\u5730\u5740\u8fdb\u884c\u5b89\u88c5\uff1a $ pip install git+https://github.com/HttpRunner/HttpRunner.git@master","title":"\u5b89\u88c5\u65b9\u5f0f"},{"location":"Installation/#_3","text":"\u5047\u5982\u4f60\u4e4b\u524d\u5df2\u7ecf\u5b89\u88c5\u8fc7\u4e86 HttpRunner\uff0c\u73b0\u5728\u9700\u8981\u5347\u7ea7\u5230\u6700\u65b0\u7248\u672c\uff0c\u90a3\u4e48\u4f60\u53ef\u4ee5\u4f7f\u7528 -U \u53c2\u6570\u3002\u8be5\u53c2\u6570\u5bf9\u4ee5\u4e0a\u4e09\u79cd\u5b89\u88c5\u65b9\u5f0f\u5747\u751f\u6548\u3002 $ pip install -U HttpRunner $ pip install -U git+https://github.com/HttpRunner/HttpRunner.git@master","title":"\u7248\u672c\u5347\u7ea7"},{"location":"Installation/#_4","text":"\u5728 HttpRunner \u5b89\u88c5\u6210\u529f\u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e\u5982\u4e0b 5 \u4e2a\u547d\u4ee4\uff1a httprunner : \u6838\u5fc3\u547d\u4ee4 ate : \u66fe\u7ecf\u7528\u8fc7\u7684\u547d\u4ee4\uff08\u5f53\u65f6\u6846\u67b6\u540d\u79f0\u4e3a ApiTestEngine\uff09\uff0c\u529f\u80fd\u4e0e httprunner \u5b8c\u5168\u76f8\u540c hrun : httprunner \u7684\u7f29\u5199\uff0c\u529f\u80fd\u4e0e httprunner \u5b8c\u5168\u76f8\u540c locusts : \u57fa\u4e8e Locust \u5b9e\u73b0 \u6027\u80fd\u6d4b\u8bd5 har2case : \u8f85\u52a9\u5de5\u5177\uff0c\u53ef\u5c06\u6807\u51c6\u901a\u7528\u7684 HAR \u683c\u5f0f\uff08HTTP Archive\uff09\u8f6c\u6362\u4e3a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b httprunner\u3001hrun\u3001ate \u4e09\u4e2a\u547d\u4ee4\u5b8c\u5168\u7b49\u4ef7\uff0c\u529f\u80fd\u7279\u6027\u5b8c\u5168\u76f8\u540c\uff0c\u4e2a\u4eba\u63a8\u8350\u4f7f\u7528 hrun \u547d\u4ee4\u3002 \u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff0c\u82e5\u6b63\u5e38\u663e\u793a\u7248\u672c\u53f7\uff0c\u5219\u8bf4\u660e HttpRunner \u5b89\u88c5\u6210\u529f\u3002 $ hrun -V 2.0.2 $ har2case -V 0.2.0","title":"\u5b89\u88c5\u6821\u9a8c"},{"location":"Installation/#_5","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5b89\u88c5 HttpRunner \u7684\u65f6\u5019\u53ea\u4f1a\u5b89\u88c5\u8fd0\u884c HttpRunner \u7684\u5fc5\u8981\u4f9d\u8d56\u5e93\u3002 \u5982\u679c\u4f60\u4e0d\u4ec5\u4ec5\u662f\u4f7f\u7528 HttpRunner\uff0c\u8fd8\u9700\u8981\u5bf9 HttpRunner \u8fdb\u884c\u5f00\u53d1\u8c03\u8bd5\uff08debug\uff09\uff0c\u90a3\u4e48\u5c31\u9700\u8981\u8fdb\u884c\u5982\u4e0b\u64cd\u4f5c\u3002 HttpRunner \u4f7f\u7528 pipenv \u5bf9\u4f9d\u8d56\u5305\u8fdb\u884c\u7ba1\u7406\uff0c\u82e5\u4f60\u8fd8\u6ca1\u6709\u5b89\u88c5 pipenv\uff0c\u9700\u8981\u5148\u6267\u884c\u5982\u4e0b\u547d\u4ee4\u8fdb\u884c\u6309\u7167\uff1a $ pip install pipenv \u83b7\u53d6 HttpRunner \u6e90\u7801\uff1a $ git clone https://github.com/HttpRunner/HttpRunner.git \u8fdb\u5165\u4ed3\u5e93\u76ee\u5f55\uff0c\u5b89\u88c5\u6240\u6709\u4f9d\u8d56\uff1a $ pipenv install --dev \u8fd0\u884c\u5355\u5143\u6d4b\u8bd5\uff0c\u82e5\u6d4b\u8bd5\u5168\u90e8\u901a\u8fc7\uff0c\u5219\u8bf4\u660e\u73af\u5883\u6b63\u5e38\u3002 $ pipenv run python -m unittest discover \u67e5\u770b HttpRunner \u7684\u4f9d\u8d56\u60c5\u51b5\uff1a $ pipenv graph HttpRunner==2.0.0 - colorama [required: Any, installed: 0.4.0] - colorlog [required: Any, installed: 3.1.4] - har2case [required: Any, installed: 0.2.0] - PyYAML [required: Any, installed: 3.13] - Jinja2 [required: Any, installed: 2.10] - MarkupSafe [required: >=0.23, installed: 1.0] - PyYAML [required: Any, installed: 3.13] - requests [required: Any, installed: 2.20.0] - certifi [required: >=2017.4.17, installed: 2018.10.15] - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4] - idna [required: >=2.5,<2.8, installed: 2.7] - urllib3 [required: >=1.21.1,<1.25, installed: 1.24] - requests-toolbelt [required: Any, installed: 0.8.0] - requests [required: >=2.0.1,<3.0.0, installed: 2.20.0] - certifi [required: >=2017.4.17, installed: 2018.10.15] - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4] - idna [required: >=2.5,<2.8, installed: 2.7] - urllib3 [required: >=1.21.1,<1.25, installed: 1.24] \u8c03\u8bd5\u8fd0\u884c\u65b9\u5f0f\uff1a # \u8c03\u8bd5\u8fd0\u884c hrun $ pipenv run python main-debug.py hrun -h # \u8c03\u8bd5\u8fd0\u884c locusts $ pipenv run python main-debug.py locusts -h","title":"\u5f00\u53d1\u8005\u6a21\u5f0f"},{"location":"Installation/#docker","text":"TODO","title":"Docker"},{"location":"quickstart/","text":"\u672c\u6587\u5c06\u901a\u8fc7\u4e00\u4e2a\u7b80\u5355\u7684\u793a\u4f8b\u6765\u5c55\u793a HttpRunner \u7684\u6838\u5fc3\u529f\u80fd\u4f7f\u7528\u65b9\u6cd5\u3002 \u6848\u4f8b\u4ecb\u7ecd \u00b6 \u8be5\u6848\u4f8b\u4f5c\u4e3a\u88ab\u6d4b\u670d\u52a1\uff0c\u4e3b\u8981\u6709\u4e24\u7c7b\u63a5\u53e3\uff1a \u6743\u9650\u6821\u9a8c\uff0c\u83b7\u53d6 token \u652f\u6301 CRUD \u64cd\u4f5c\u7684 RESTful APIs\uff0c\u6240\u6709\u63a5\u53e3\u7684\u8bf7\u6c42\u5934\u57df\u4e2d\u90fd\u5fc5\u987b\u5305\u542b\u6709\u6548\u7684 token \u6848\u4f8b\u7684\u5b9e\u73b0\u5f62\u5f0f\u4e3a flask \u5e94\u7528\u670d\u52a1\uff08 api_server.py \uff09\uff0c\u542f\u52a8\u65b9\u5f0f\u5982\u4e0b\uff1a $ export FLASK_APP=docs/data/api_server.py $ export FLASK_ENV=development $ flask run * Serving Flask app \"docs/data/api_server.py\" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 989-476-348 \u670d\u52a1\u542f\u52a8\u6210\u529f\u540e\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u5f00\u59cb\u5bf9\u5176\u8fdb\u884c\u6d4b\u8bd5\u4e86\u3002 \u6d4b\u8bd5\u51c6\u5907 \u00b6 \u6293\u5305\u5206\u6790 \u00b6 \u5728\u5f00\u59cb\u6d4b\u8bd5\u4e4b\u524d\uff0c\u6211\u4eec\u9700\u8981\u5148\u4e86\u89e3\u63a5\u53e3\u7684\u8bf7\u6c42\u548c\u54cd\u5e94\u7ec6\u8282\uff0c\u800c\u6700\u4f73\u7684\u65b9\u5f0f\u5c31\u662f\u91c7\u7528 Charles Proxy \u6216\u8005 Fiddler \u8fd9\u7c7b\u7f51\u7edc\u6293\u5305\u5de5\u5177\u8fdb\u884c\u6293\u5305\u5206\u6790\u3002 \u4f8b\u5982\uff0c\u5728\u672c\u6848\u4f8b\u4e2d\uff0c\u6211\u4eec\u5148\u8fdb\u884c\u6743\u9650\u6821\u9a8c\uff0c\u7136\u540e\u6210\u529f\u521b\u5efa\u4e00\u4e2a\u7528\u6237\uff0c\u5bf9\u5e94\u7684\u7f51\u7edc\u6293\u5305\u5185\u5bb9\u5982\u4e0b\u56fe\u6240\u793a\uff1a \u901a\u8fc7\u6293\u5305\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u5177\u4f53\u7684\u63a5\u53e3\u4fe1\u606f\uff0c\u5305\u62ec\u8bf7\u6c42\u7684URL\u3001Method\u3001headers\u3001\u53c2\u6570\u548c\u54cd\u5e94\u5185\u5bb9\u7b49\u5185\u5bb9\uff0c\u57fa\u4e8e\u8fd9\u4e9b\u4fe1\u606f\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u5f00\u59cb\u7f16\u5199\u6d4b\u8bd5\u7528\u4f8b\u4e86\u3002 \u751f\u6210\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u4e3a\u4e86\u7b80\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u7f16\u5199\u5de5\u4f5c\uff0cHttpRunner \u5b9e\u73b0\u4e86\u6d4b\u8bd5\u7528\u4f8b\u751f\u6210\u7684\u529f\u80fd\u3002 \u9996\u5148\uff0c\u9700\u8981\u5c06\u6293\u53d6\u5f97\u5230\u7684\u6570\u636e\u5305\u5bfc\u51fa\u4e3a HAR \u683c\u5f0f\u7684\u6587\u4ef6\uff0c\u5047\u8bbe\u5bfc\u51fa\u7684\u6587\u4ef6\u540d\u79f0\u4e3a demo-quickstart.har \u3002 \u7136\u540e\uff0c\u5728\u547d\u4ee4\u884c\u7ec8\u7aef\u4e2d\u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff0c\u5373\u53ef\u5c06 demo-quickstart.har \u8f6c\u6362\u4e3a HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 $ har2case docs/data/demo-quickstart.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml \u4f7f\u7528 har2case \u8f6c\u6362\u811a\u672c\u65f6\u9ed8\u8ba4\u8f6c\u6362\u4e3a JSON \u683c\u5f0f\uff0c\u52a0\u4e0a -2y \u53c2\u6570\u540e\u8f6c\u6362\u4e3a YAML \u683c\u5f0f\u3002\u4e24\u79cd\u683c\u5f0f\u5b8c\u5168\u7b49\u4ef7\uff0cYAML \u683c\u5f0f\u66f4\u7b80\u6d01\uff0cJSON \u683c\u5f0f\u652f\u6301\u7684\u5de5\u5177\u66f4\u4e30\u5bcc\uff0c\u5927\u5bb6\u53ef\u6839\u636e\u4e2a\u4eba\u559c\u597d\u8fdb\u884c\u9009\u62e9\u3002\u5173\u4e8e har2case \u7684\u8be6\u7ec6\u4f7f\u7528\u8bf4\u660e\uff0c\u8bf7\u67e5\u770b \u300a\u5f55\u5236\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u300b \u3002 \u7ecf\u8fc7\u8f6c\u6362\uff0c\u5728\u6e90 demo-quickstart.har \u6587\u4ef6\u7684\u540c\u7ea7\u76ee\u5f55\u4e0b\u751f\u6210\u4e86\u76f8\u540c\u6587\u4ef6\u540d\u79f0\u7684 YAML \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6 demo-quickstart.yml \uff0c\u5176\u5185\u5bb9\u5982\u4e0b\uff1a - config : name : testcase description variables : {} - test : name : /api/get-token request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 app_version : 2.8.6 device_sn : FwgRiO7CNA50DSU os_platform : ios json : sign : 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 method : POST url : http://127.0.0.1:5000/api/get-token validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] - eq : [ content.token , baNLX1zhFYP11Seb ] - test : name : /api/users/1000 request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 device_sn : FwgRiO7CNA50DSU token : baNLX1zhFYP11Seb json : name : user1 password : '123456' method : POST url : http://127.0.0.1:5000/api/users/1000 validate : - eq : [ status_code , 201 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] - eq : [ content.msg , user created successfully. ] \u73b0\u5728\u6211\u4eec\u53ea\u9700\u8981\u77e5\u9053\u5982\u4e0b\u51e0\u70b9\uff1a \u6bcf\u4e2a YAML/JSON \u6587\u4ef6\u5bf9\u5e94\u4e00\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09 \u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u4e3a\u4e00\u4e2a list of dict \u7ed3\u6784\uff0c\u5176\u4e2d\u53ef\u80fd\u5305\u542b\u5168\u5c40\u914d\u7f6e\u9879\uff08config\uff09\u548c\u82e5\u5e72\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09 config \u4e3a\u5168\u5c40\u914d\u7f6e\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b test \u5bf9\u5e94\u5355\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u4f5c\u7528\u57df\u4ec5\u9650\u4e8e\u672c\u8eab \u5982\u4e0a\u4fbf\u662f HttpRunner \u6d4b\u8bd5\u7528\u4f8b\u7684\u57fa\u672c\u7ed3\u6784\u3002 \u5173\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u7684\u66f4\u591a\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u63cf\u8ff0\u300b \u3002 \u9996\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u6d4b\u8bd5\u7528\u4f8b\u5c31\u7eea\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5f00\u59cb\u8c03\u8bd5\u8fd0\u884c\u4e86\u3002 \u4e3a\u4e86\u6f14\u793a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8fed\u4ee3\u4f18\u5316\u8fc7\u7a0b\uff0c\u6211\u4eec\u5148\u5c06 demo-quickstart.json \u91cd\u547d\u540d\u4e3a demo-quickstart-0.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-0.yml \uff09\u3002 \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u547d\u4ee4\u4e3a hrun \uff0c\u540e\u9762\u76f4\u63a5\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\u5373\u53ef\u3002 $ hrun docs/data/demo-quickstart-0.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 9.26 ms, response_length: 46 bytes ERROR validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/get-token method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios'} json: {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'} verify: True ====== response details ====== status_code: 200 headers: {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'} body: '{\"success\": true, \"token\": \"tXGuSQgOCVXcltkz\"}' F /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000 ERROR validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) ERROR validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) ERROR validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/users/1000 method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'} json: {'name': 'user1', 'password': '123456'} verify: True ====== response details ====== status_code: 403 headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'} body: '{\"success\": false, \"msg\": \"Authorization failed!\"}' F ====================================================================== FAIL: test_0000_000 (httprunner.api.TestSequense) /api/get-token ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) ====================================================================== FAIL: test_0001_000 (httprunner.api.TestSequense) /api/users/1000 ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ---------------------------------------------------------------------- Ran 2 tests in 0.026s FAILED (failures=2) INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513835.html \u975e\u5e38\u4e0d\u5e78\uff0c\u4e24\u4e2a\u63a5\u53e3\u7684\u6d4b\u8bd5\u7528\u4f8b\u5747\u8fd0\u884c\u5931\u8d25\u4e86\u3002 \u4f18\u5316\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u4ece\u4e24\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u62a5\u9519\u4fe1\u606f\u548c\u5806\u6808\u4fe1\u606f\uff08Traceback\uff09\u53ef\u4ee5\u770b\u51fa\uff0c\u7b2c\u4e00\u4e2a\u6b65\u9aa4\u5931\u8d25\u7684\u539f\u56e0\u662f\u83b7\u53d6\u7684 token \u4e0e\u9884\u671f\u503c\u4e0d\u4e00\u81f4\uff0c\u7b2c\u4e8c\u4e2a\u6b65\u9aa4\u5931\u8d25\u7684\u539f\u56e0\u662f\u8bf7\u6c42\u6743\u9650\u6821\u9a8c\u5931\u8d25\uff08403\uff09\u3002 \u63a5\u4e0b\u6765\u6211\u4eec\u5c06\u9010\u6b65\u8fdb\u884c\u8fdb\u884c\u4f18\u5316\u3002 \u8c03\u6574\u6821\u9a8c\u5668 \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c har2case \u751f\u6210\u7528\u4f8b\u65f6\uff0c\u82e5 HTTP \u8bf7\u6c42\u7684\u54cd\u5e94\u5185\u5bb9\u4e3a JSON \u683c\u5f0f\uff0c\u5219\u4f1a\u5c06\u7b2c\u4e00\u5c42\u7ea7\u4e2d\u7684\u6240\u6709 key-value \u8f6c\u6362\u4e3a validator\u3002 \u4f8b\u5982\u4e0a\u9762\u7684\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u751f\u6210\u7684 validator \u4e3a\uff1a \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]}, { \"eq\" : [ \"content.token\" , \"baNLX1zhFYP11Seb\" ]} ] \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\u5c31\u4f1a\u5bf9\u4e0a\u9762\u7684\u5404\u4e2a\u9879\u8fdb\u884c\u6821\u9a8c\u3002 \u95ee\u9898\u5728\u4e8e\uff0c\u8bf7\u6c42 /api/get-token \u63a5\u53e3\u65f6\uff0c\u6bcf\u6b21\u751f\u6210\u7684 token \u90fd\u4f1a\u662f\u4e0d\u540c\u7684\uff0c\u56e0\u6b64\u5c06\u751f\u6210\u7684 token \u4f5c\u4e3a\u6821\u9a8c\u9879\u7684\u8bdd\uff0c\u6821\u9a8c\u81ea\u7136\u5c31\u65e0\u6cd5\u901a\u8fc7\u4e86\u3002 \u6b63\u786e\u7684\u505a\u6cd5\u662f\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u7684 validate \u4e2d\u5e94\u8be5\u53bb\u6389\u8fd9\u7c7b\u52a8\u6001\u53d8\u5316\u7684\u503c\u3002 \u53bb\u9664\u8be5\u9879\u540e\uff0c\u5c06\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-1.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-1.yml \uff09\u3002 \u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u8fd0\u884c\u7ed3\u679c\u5982\u4e0b\uff1a $ hrun docs/data/demo-quickstart-1.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 6.61 ms, response_length: 46 bytes . /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000 ERROR validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) ERROR validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) ERROR validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/users/1000 method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'} json: {'name': 'user1', 'password': '123456'} verify: True ====== response details ====== status_code: 403 headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:45:34 GMT'} body: '{\"success\": false, \"msg\": \"Authorization failed!\"}' F ====================================================================== FAIL: test_0001_000 (httprunner.api.TestSequense) /api/users/1000 ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ---------------------------------------------------------------------- Ran 2 tests in 0.018s FAILED (failures=1) INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513934.html \u7ecf\u8fc7\u4fee\u6539\uff0c\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5df2\u7ecf\u8fd0\u884c\u6210\u529f\u4e86\uff0c\u7b2c\u4e8c\u4e2a\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u5931\u8d25\uff08403\uff09\uff0c\u8fd8\u662f\u56e0\u4e3a\u6743\u9650\u6821\u9a8c\u7684\u539f\u56e0\u3002 \u53c2\u6570\u5173\u8054 \u00b6 \u6211\u4eec\u7ee7\u7eed\u67e5\u770b demo-quickstart-1.json \uff0c\u4f1a\u53d1\u73b0\u7b2c\u4e8c\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u8bf7\u6c42 headers \u4e2d\u7684 token \u4ecd\u7136\u662f\u786c\u7f16\u7801\u7684\uff0c\u5373\u6293\u5305\u65f6\u83b7\u53d6\u5230\u7684\u503c\u3002\u5728\u6211\u4eec\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\u8fd9\u4e2a token \u5df2\u7ecf\u5931\u6548\u4e86\uff0c\u6240\u4ee5\u4f1a\u51fa\u73b0 403 \u6743\u9650\u6821\u9a8c\u5931\u8d25\u7684\u95ee\u9898\u3002 \u6b63\u786e\u7684\u505a\u6cd5\u662f\uff0c\u6211\u4eec\u5e94\u8be5\u5728\u6bcf\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u65f6\u5019\uff0c\u5148\u52a8\u6001\u83b7\u53d6\u5230\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u7684 token\uff0c\u7136\u540e\u5728\u540e\u7eed\u6d4b\u8bd5\u6b65\u9aa4\u7684\u8bf7\u6c42\u4e2d\u4f7f\u7528\u524d\u9762\u83b7\u53d6\u5230\u7684 token\u3002 \u5728 HttpRunner \u4e2d\uff0c\u652f\u6301\u53c2\u6570\u63d0\u53d6\uff08 extract \uff09\u548c\u53c2\u6570\u5f15\u7528\u7684\u529f\u80fd\uff08 $var \uff09\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u4e2d\uff0c\u82e5\u9700\u8981\u4ece\u54cd\u5e94\u7ed3\u679c\u4e2d\u63d0\u53d6\u53c2\u6570\uff0c\u5219\u53ef\u4f7f\u7528 extract \u5173\u952e\u5b57\u3002extract \u7684\u5217\u8868\u4e2d\u53ef\u6307\u5b9a\u4e00\u4e2a\u6216\u591a\u4e2a\u9700\u8981\u63d0\u53d6\u7684\u53c2\u6570\u3002 \u5728\u63d0\u53d6\u53c2\u6570\u65f6\uff0c\u5f53 HTTP \u7684\u8bf7\u6c42\u54cd\u5e94\u7ed3\u679c\u4e3a JSON \u683c\u5f0f\uff0c\u5219\u53ef\u4ee5\u91c7\u7528 . \u8fd0\u7b97\u7b26\u7684\u65b9\u5f0f\uff0c\u9010\u7ea7\u5f80\u4e0b\u83b7\u53d6\u5230\u53c2\u6570\u503c\uff1b\u54cd\u5e94\u7ed3\u679c\u7684\u6574\u4f53\u5185\u5bb9\u5f15\u7528\u65b9\u5f0f\u4e3a content \u6216\u8005 body\u3002 \u4f8b\u5982\uff0c\u7b2c\u4e00\u4e2a\u63a5\u53e3 /api/get-token \u7684\u54cd\u5e94\u7ed3\u679c\u4e3a\uff1a { \"success\" : true , \"token\" : \"ZQkYhbaQ6q8UFFNE\" } \u90a3\u4e48\u8981\u83b7\u53d6\u5230 token \u53c2\u6570\uff0c\u5c31\u53ef\u4ee5\u4f7f\u7528 content.token \u7684\u65b9\u5f0f\uff1b\u5177\u4f53\u7684\u5199\u6cd5\u5982\u4e0b\uff1a \"extract\" : [ { \"token\" : \"content.token\" } ] \u5176\u4e2d\uff0ctoken \u4f5c\u4e3a\u63d0\u53d6\u540e\u7684\u53c2\u6570\u540d\u79f0\uff0c\u53ef\u4ee5\u5728\u540e\u7eed\u4f7f\u7528 $token \u8fdb\u884c\u5f15\u7528\u3002 \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"$token\" , \"Content-Type\" : \"application/json\" } \u4fee\u6539\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-2.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-2.yml \uff09\u3002 \u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u8fd0\u884c\u7ed3\u679c\u5982\u4e0b\uff1a $ hrun docs/data/demo-quickstart-2.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.32 ms, response_length: 46 bytes . /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.019s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548514191.html \u7ecf\u8fc7\u4fee\u6539\uff0c\u7b2c\u4e8c\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e5f\u8fd0\u884c\u6210\u529f\u4e86\u3002 base_url \u00b6 \u867d\u7136\u6d4b\u8bd5\u6b65\u9aa4\u8fd0\u884c\u90fd\u6210\u529f\u4e86\uff0c\u4f46\u662f\u4ecd\u7136\u6709\u7ee7\u7eed\u4f18\u5316\u7684\u5730\u65b9\u3002 \u7ee7\u7eed\u67e5\u770b demo-quickstart-2.json \uff0c\u6211\u4eec\u4f1a\u53d1\u73b0\u5728\u6bcf\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684 URL \u4e2d\uff0c\u90fd\u91c7\u7528\u7684\u662f\u5b8c\u6574\u7684\u63cf\u8ff0\uff08host+path\uff09\uff0c\u4f46\u5927\u591a\u6570\u60c5\u51b5\u4e0b\u540c\u4e00\u4e2a\u7528\u4f8b\u4e2d\u7684 host \u90fd\u662f\u76f8\u540c\u7684\uff0c\u533a\u522b\u4ec5\u5728\u4e8e path \u90e8\u5206\u3002 \u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09 URL \u7684 base_url \u62bd\u53d6\u51fa\u6765\uff0c\u653e\u5230\u5168\u5c40\u914d\u7f6e\u6a21\u5757\uff08config\uff09\u4e2d\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u7684 URL \u53ea\u4fdd\u7559 PATH \u90e8\u5206\u3002 - config : name : testcase description base_url : http://127.0.0.1:5000 - test : name : get token request : url : /api/get-token \u8c03\u6574\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-3.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-3.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002 \u53d8\u91cf\u7684\u7533\u660e\u548c\u5f15\u7528 \u00b6 \u7ee7\u7eed\u67e5\u770b demo-quickstart-3.json \uff0c\u6211\u4eec\u4f1a\u53d1\u73b0\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b58\u5728\u8f83\u591a\u786c\u7f16\u7801\u7684\u53c2\u6570\uff0c\u4f8b\u5982 app_version\u3001device_sn\u3001os_platform\u3001user_id \u7b49\u3002 \u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4e0d\u7528\u4fee\u6539\u8fd9\u4e9b\u786c\u7f16\u7801\u7684\u53c2\u6570\uff0c\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u80fd\u6b63\u5e38\u8fd0\u884c\u3002\u4f46\u662f\u4e3a\u4e86\u66f4\u597d\u5730\u7ef4\u62a4\u6d4b\u8bd5\u7528\u4f8b\uff0c\u4f8b\u5982\u540c\u4e00\u4e2a\u53c2\u6570\u503c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u51fa\u73b0\u591a\u6b21\uff0c\u90a3\u4e48\u6bd4\u8f83\u597d\u7684\u505a\u6cd5\u662f\uff0c\u5c06\u8fd9\u4e9b\u53c2\u6570\u5b9a\u4e49\u4e3a\u53d8\u91cf\uff0c\u7136\u540e\u5728\u9700\u8981\u53c2\u6570\u7684\u5730\u65b9\u8fdb\u884c\u5f15\u7528\u3002 \u5728 HttpRunner \u4e2d\uff0c\u652f\u6301\u53d8\u91cf\u7533\u660e\uff08 variables \uff09\u548c\u5f15\u7528\uff08 $var \uff09\u7684\u673a\u5236\u3002\u5728 config \u548c test \u4e2d\u5747\u53ef\u4ee5\u901a\u8fc7 variables \u5173\u952e\u5b57\u5b9a\u4e49\u53d8\u91cf\uff0c\u7136\u540e\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u53ef\u4ee5\u901a\u8fc7 $ + \u53d8\u91cf\u540d\u79f0 \u7684\u65b9\u5f0f\u5f15\u7528\u53d8\u91cf\u3002\u533a\u522b\u5728\u4e8e\uff0c\u5728 config \u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u4e3a\u5168\u5c40\u7684\uff0c\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u7684\u6240\u6709\u5730\u65b9\u5747\u53ef\u4ee5\u5f15\u7528\uff1b\u5728 test \u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u4f5c\u7528\u57df\u4ec5\u5c40\u9650\u4e8e\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u3002 \u5bf9\u4e0a\u8ff0\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u786c\u7f16\u7801\u7684\u53c2\u6570\u8fdb\u884c\u53d8\u91cf\u7533\u660e\u548c\u5f15\u7528\u8c03\u6574\u540e\uff0c\u65b0\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-4.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-4.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002 \u62bd\u53d6\u516c\u5171\u53d8\u91cf \u00b6 \u67e5\u770b demo-quickstart-4.json \u53ef\u4ee5\u770b\u51fa\uff0c\u4e24\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u90fd\u5b9a\u4e49\u4e86 device_sn\u3002\u9488\u5bf9\u8fd9\u7c7b\u516c\u5171\u7684\u53c2\u6570\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u7edf\u4e00\u5b9a\u4e49\u5728 config \u7684 variables \u4e2d\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5c31\u4e0d\u7528\u518d\u91cd\u590d\u5b9a\u4e49\u3002 - config : name : testcase description base_url : http://127.0.0.1:5000 variables : device_sn : FwgRiO7CNA50DSU \u8c03\u6574\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u89c1 demo-quickstart-5.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-5.yml \uff09\u3002 \u5b9e\u73b0\u52a8\u6001\u8fd0\u7b97\u903b\u8f91 \u00b6 \u5728 demo-quickstart-5.yml \u4e2d\uff0c\u53c2\u6570 device_sn \u4ee3\u8868\u7684\u662f\u8bbe\u5907\u7684 SN \u7f16\u7801\uff0c\u867d\u7136\u91c7\u7528\u786c\u7f16\u7801\u7684\u65b9\u5f0f\u6682\u65f6\u4e0d\u5f71\u54cd\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\uff0c\u4f46\u8fd9\u4e0e\u771f\u5b9e\u7684\u7528\u6237\u573a\u666f\u4e0d\u5927\u76f8\u7b26\u3002 \u5047\u8bbe device_sn \u7684\u683c\u5f0f\u4e3a 15 \u957f\u5ea6\u7684\u5b57\u7b26\u4e32\uff0c\u90a3\u4e48\u6211\u4eec\u5c31\u53ef\u4ee5\u5728\u6bcf\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u65f6\u5019\uff0c\u9488\u5bf9 device_sn \u751f\u6210\u4e00\u4e2a 15 \u4f4d\u957f\u5ea6\u7684\u968f\u673a\u5b57\u7b26\u4e32\u3002\b\u4e0e\u6b64\u540c\u65f6\uff0csign \u5b57\u6bb5\u662f\u6839\u636e headers \u4e2d\u7684\u5404\u4e2a\u5b57\u6bb5\u62fc\u63a5\u540e\u751f\u6210\u5f97\u5230\u7684 MD5 \u503c\uff0c\u56e0\u6b64\u5728 device_sn \u53d8\u52a8\u540e\uff0csign \u4e5f\u5e94\u8be5\u91cd\u65b0\u8fdb\u884c\u8ba1\u7b97\uff0c\u5426\u5219\u5c31\u4f1a\u518d\u6b21\u51fa\u73b0\u7b7e\u540d\u6821\u9a8c\u5931\u8d25\u7684\u95ee\u9898\u3002 \u7136\u800c\uff0cHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u90fd\u662f\u91c7\u7528 YAML/JSON \u683c\u5f0f\u8fdb\u884c\u63cf\u8ff0\u7684\uff0c\u5728\u6587\u672c\u683c\u5f0f\u4e2d\u5982\u4f55\u6267\u884c\u4ee3\u7801\u8fd0\u7b97\u5462\uff1f HttpRunner \u7684\u5b9e\u73b0\u65b9\u5f0f\u4e3a\uff0c\u652f\u6301\u70ed\u52a0\u8f7d\u7684\u63d2\u4ef6\u673a\u5236\uff08 debugtalk.py \uff09\uff0c\u53ef\u4ee5\u5728 YAML/JSON \u4e2d\u8c03\u7528 Python \u51fd\u6570\u3002 \u5177\u4f53\u5730\u505a\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u540c\u7ea7\u6216\u5176\u7236\u7ea7\u76ee\u5f55\u4e2d\u521b\u5efa\u4e00\u4e2a debugtalk.py \u6587\u4ef6\uff0c\u7136\u540e\u5728\u5176\u4e2d\u5b9a\u4e49\u76f8\u5173\u7684\u51fd\u6570\u548c\u53d8\u91cf\u3002 \u4f8b\u5982\uff0c\u9488\u5bf9 device_sn \u7684\u968f\u673a\u5b57\u7b26\u4e32\u751f\u6210\u529f\u80fd\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u4e00\u4e2a gen_random_string \u51fd\u6570\uff1b\u9488\u5bf9 sign \u7684\u7b7e\u540d\u7b97\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u4e00\u4e2a get_sign \u51fd\u6570\u3002 import hashlib import hmac import random import string SECRET_KEY = \"DebugTalk\" def gen_random_string ( str_len ): random_char_list = [] for _ in range ( str_len ): random_char = random . choice ( string . ascii_letters + string . digits ) random_char_list . append ( random_char ) random_string = '' . join ( random_char_list ) return random_string def get_sign ( * args ): content = '' . join ( args ) . encode ( 'ascii' ) sign_key = SECRET_KEY . encode ( 'ascii' ) sign = hmac . new ( sign_key , content , hashlib . sha1 ) . hexdigest () return sign \u7136\u540e\uff0c\u6211\u4eec\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u5bf9\u5b9a\u4e49\u7684\u51fd\u6570\u8fdb\u884c\u8c03\u7528\uff0c\u5bf9\u5b9a\u4e49\u7684\u53d8\u91cf\u8fdb\u884c\u5f15\u7528\u4e86\u3002\u5f15\u7528\u53d8\u91cf\u7684\u65b9\u5f0f\u4ecd\u7136\u4e0e\u524d\u9762\u8bb2\u7684\u4e00\u6837\uff0c\u91c7\u7528 $ + \u53d8\u91cf\u540d\u79f0 \u7684\u65b9\u5f0f\uff1b\u8c03\u7528\u51fd\u6570\u7684\u65b9\u5f0f\u4e3a ${func($var)} \u3002 \u4f8b\u5982\uff0c\u751f\u6210 15 \u4f4d\u957f\u5ea6\u7684\u968f\u673a\u5b57\u7b26\u4e32\u5e76\u8d4b\u503c\u7ed9 device_sn \u7684\u4ee3\u7801\u4e3a\uff1a \"variables\" : [ { \"device_sn\" : \"${gen_random_string(15)}\" } ] \u4f7f\u7528 $user_agent\u3001$device_sn\u3001$os_platform\u3001$app_version \u6839\u636e\u7b7e\u540d\u7b97\u6cd5\u751f\u6210 sign \u503c\u7684\u4ee3\u7801\u4e3a\uff1a \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" } \u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u4e0a\u8ff0\u8c03\u6574\u540e\uff0c\u53e6\u5b58\u4e3a demo-quickstart-6.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-6.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002 \u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8 \u00b6 \u8bf7\u786e\u4fdd\u4f60\u4f7f\u7528\u7684 HttpRunner \u7248\u672c\u53f7\u4e0d\u4f4e\u4e8e 2.0.0 \u5728 demo-quickstart-6.yml \u4e2d\uff0cuser_id \u4ecd\u7136\u662f\u5199\u6b7b\u7684\u503c\uff0c\u5047\u5982\u6211\u4eec\u9700\u8981\u521b\u5efa user_id \u4e3a 1001\uff5e1004 \u7684\u7528\u6237\uff0c\u90a3\u6211\u4eec\u53ea\u80fd\u4e0d\u65ad\u5730\u53bb\u4fee\u6539 user_id\uff0c\u7136\u540e\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u91cd\u590d\u64cd\u4f5c 4 \u6b21\uff1f\u6216\u8005\u6211\u4eec\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u5c06\u521b\u5efa\u7528\u6237\u7684 test \u590d\u5236 4 \u4efd\uff0c\u7136\u540e\u5728\u6bcf\u4e00\u4efd\u91cc\u9762\u5206\u522b\u4f7f\u7528\u4e0d\u540c\u7684 user_id \uff1f \u5f88\u663e\u7136\uff0c\u4e0d\u7ba1\u662f\u91c7\u7528\u4e0a\u8ff0\u54ea\u79cd\u65b9\u5f0f\uff0c\u90fd\u4f1a\u5f88\u7e41\u7410\uff0c\u5e76\u4e14\u4e5f\u65e0\u6cd5\u5e94\u5bf9\u7075\u6d3b\u591a\u53d8\u7684\u6d4b\u8bd5\u9700\u6c42\u3002 \u9488\u5bf9\u8fd9\u7c7b\u9700\u6c42\uff0cHttpRunner \u652f\u6301\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u7684\u529f\u80fd\u3002 \u5728 HttpRunner \u4e2d\uff0c\u82e5\u8981\u91c7\u7528\u6570\u636e\u9a71\u52a8\u7684\u65b9\u5f0f\u6765\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u9700\u8981\u521b\u5efa\u4e00\u4e2a\u6587\u4ef6\uff0c\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u5f15\u7528\uff0c\u5e76\u4f7f\u7528 parameters \u5173\u952e\u5b57\u5b9a\u4e49\u53c2\u6570\u5e76\u6307\u5b9a\u6570\u636e\u6e90\u53d6\u503c\u65b9\u5f0f\u3002 \u4f8b\u5982\uff0c\u6211\u4eec\u9700\u8981\u5728\u521b\u5efa\u7528\u6237\u7684\u63a5\u53e3\u4e2d\u5bf9 user_id \u8fdb\u884c\u53c2\u6570\u5316\uff0c\u53c2\u6570\u5316\u5217\u8868\u4e3a 1001\uff5e1004\uff0c\u5e76\u4e14\u53d6\u503c\u65b9\u5f0f\u4e3a\u987a\u5e8f\u53d6\u503c\uff0c\u90a3\u4e48\u6700\u7b80\u5355\u7684\u63cf\u8ff0\u65b9\u5f0f\u5c31\u662f\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\u3002\u5177\u4f53\u7684\u7f16\u5199\u65b9\u5f0f\u4e3a\uff0c\u65b0\u5efa\u4e00\u4e2a\u6d4b\u8bd5\u573a\u666f\u6587\u4ef6 demo-quickstart-7.yml \uff08\u5bf9\u5e94\u7684 JSON \u683c\u5f0f\uff1a demo-quickstart-7.json \uff09\uff0c\u5185\u5bb9\u5982\u4e0b\u6240\u793a\uff1a config : name : testcase description testcases : create user : testcase : demo-quickstart-6.yml parameters : user_id : [ 1001 , 1002 , 1003 , 1004 ] \u4ec5\u9700\u5982\u4e0a\u914d\u7f6e\uff0c\u9488\u5bf9 user_id \u7684\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u5c31\u5b8c\u6210\u4e86\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u60c5\u51b5\u5982\u4e0b\u6240\u793a\uff1a \u70b9\u51fb\u67e5\u770b\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-quickstart-7.json INFO Start to run testcase: create user 1001 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes . /api/users/1001 INFO POST http://127.0.0.1:5000/api/users/1001 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.021s OK INFO Start to run testcase: create user 1002 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes . /api/users/1002 INFO POST http://127.0.0.1:5000/api/users/1002 INFO status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK INFO Start to run testcase: create user 1003 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes . /api/users/1003 INFO POST http://127.0.0.1:5000/api/users/1003 INFO status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.011s OK INFO Start to run testcase: create user 1004 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes . /api/users/1004 INFO POST http://127.0.0.1:5000/api/users/1004 INFO status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.016s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html \u53ef\u4ee5\u770b\u51fa\uff0c\u6d4b\u8bd5\u7528\u4f8b\u603b\u5171\u8fd0\u884c\u4e86 4 \u6b21\uff0c\u5e76\u4e14\u6bcf\u6b21\u8fd0\u884c\u65f6\u90fd\u662f\u91c7\u7528\u7684\u4e0d\u540c user_id\u3002 \u5173\u4e8e\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u8fd9\u91cc\u53ea\u63cf\u8ff0\u4e86\u6700\u7b80\u5355\u7684\u573a\u666f\u548c\u4f7f\u7528\u65b9\u5f0f\uff0c\u5982\u9700\u4e86\u89e3\u66f4\u591a\uff0c\u8bf7\u8fdb\u4e00\u6b65\u9605\u8bfb \u300a\u6570\u636e\u9a71\u52a8\u4f7f\u7528\u624b\u518c\u300b \u3002 \u67e5\u770b\u6d4b\u8bd5\u62a5\u544a \u00b6 \u5728\u6bcf\u6b21\u4f7f\u7528 hrun \u547d\u4ee4\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u540e\uff0c\u5747\u4f1a\u751f\u6210\u4e00\u4efd HTML \u683c\u5f0f\u7684\u6d4b\u8bd5\u62a5\u544a\u3002\u62a5\u544a\u6587\u4ef6\u4f4d\u4e8e reports \u76ee\u5f55\u4e0b\uff0c\u6587\u4ef6\u540d\u79f0\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5f00\u59cb\u8fd0\u884c\u65f6\u95f4\u3002 \u4f8b\u5982\uff0c\u5728\u8fd0\u884c\u5b8c demo-quickstart-1.json \u540e\uff0c\u5c06\u751f\u6210\u5982\u4e0b\u5f62\u5f0f\u7684\u6d4b\u8bd5\u62a5\u544a\uff1a \u5173\u4e8e\u6d4b\u8bd5\u62a5\u544a\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u300a\u6d4b\u8bd5\u62a5\u544a\u300b \u90e8\u5206\u3002 \u603b\u7ed3 \u00b6 \u5230\u6b64\u4e3a\u6b62\uff0cHttpRunner \u7684\u6838\u5fc3\u529f\u80fd\u5c31\u4ecb\u7ecd\u5b8c\u4e86\uff0c\u638c\u63e1\u672c\u6587\u4e2d\u7684\u529f\u80fd\u7279\u6027\uff0c\u8db3\u4ee5\u5e2e\u52a9\u4f60\u5e94\u5bf9\u65e5\u5e38\u9879\u76ee\u5de5\u4f5c\u4e2d\u81f3\u5c11 80% \u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u9700\u6c42\u3002 \u5f53\u7136\uff0cHttpRunner \u4e0d\u6b62\u4e8e\u6b64\uff0c\u5982\u9700\u6316\u6398 HttpRunner \u7684\u66f4\u591a\u7279\u6027\uff0c\u5b9e\u73b0\u66f4\u590d\u6742\u573a\u666f\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u9700\u6c42\uff0c\u53ef\u7ee7\u7eed\u9605\u8bfb\u540e\u7eed\u6587\u6863\u3002","title":"\u5feb\u901f\u4e0a\u624b"},{"location":"quickstart/#_1","text":"\u8be5\u6848\u4f8b\u4f5c\u4e3a\u88ab\u6d4b\u670d\u52a1\uff0c\u4e3b\u8981\u6709\u4e24\u7c7b\u63a5\u53e3\uff1a \u6743\u9650\u6821\u9a8c\uff0c\u83b7\u53d6 token \u652f\u6301 CRUD \u64cd\u4f5c\u7684 RESTful APIs\uff0c\u6240\u6709\u63a5\u53e3\u7684\u8bf7\u6c42\u5934\u57df\u4e2d\u90fd\u5fc5\u987b\u5305\u542b\u6709\u6548\u7684 token \u6848\u4f8b\u7684\u5b9e\u73b0\u5f62\u5f0f\u4e3a flask \u5e94\u7528\u670d\u52a1\uff08 api_server.py \uff09\uff0c\u542f\u52a8\u65b9\u5f0f\u5982\u4e0b\uff1a $ export FLASK_APP=docs/data/api_server.py $ export FLASK_ENV=development $ flask run * Serving Flask app \"docs/data/api_server.py\" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 989-476-348 \u670d\u52a1\u542f\u52a8\u6210\u529f\u540e\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u5f00\u59cb\u5bf9\u5176\u8fdb\u884c\u6d4b\u8bd5\u4e86\u3002","title":"\u6848\u4f8b\u4ecb\u7ecd"},{"location":"quickstart/#_2","text":"","title":"\u6d4b\u8bd5\u51c6\u5907"},{"location":"quickstart/#_3","text":"\u5728\u5f00\u59cb\u6d4b\u8bd5\u4e4b\u524d\uff0c\u6211\u4eec\u9700\u8981\u5148\u4e86\u89e3\u63a5\u53e3\u7684\u8bf7\u6c42\u548c\u54cd\u5e94\u7ec6\u8282\uff0c\u800c\u6700\u4f73\u7684\u65b9\u5f0f\u5c31\u662f\u91c7\u7528 Charles Proxy \u6216\u8005 Fiddler \u8fd9\u7c7b\u7f51\u7edc\u6293\u5305\u5de5\u5177\u8fdb\u884c\u6293\u5305\u5206\u6790\u3002 \u4f8b\u5982\uff0c\u5728\u672c\u6848\u4f8b\u4e2d\uff0c\u6211\u4eec\u5148\u8fdb\u884c\u6743\u9650\u6821\u9a8c\uff0c\u7136\u540e\u6210\u529f\u521b\u5efa\u4e00\u4e2a\u7528\u6237\uff0c\u5bf9\u5e94\u7684\u7f51\u7edc\u6293\u5305\u5185\u5bb9\u5982\u4e0b\u56fe\u6240\u793a\uff1a \u901a\u8fc7\u6293\u5305\uff0c\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u5177\u4f53\u7684\u63a5\u53e3\u4fe1\u606f\uff0c\u5305\u62ec\u8bf7\u6c42\u7684URL\u3001Method\u3001headers\u3001\u53c2\u6570\u548c\u54cd\u5e94\u5185\u5bb9\u7b49\u5185\u5bb9\uff0c\u57fa\u4e8e\u8fd9\u4e9b\u4fe1\u606f\uff0c\u6211\u4eec\u5c31\u53ef\u4ee5\u5f00\u59cb\u7f16\u5199\u6d4b\u8bd5\u7528\u4f8b\u4e86\u3002","title":"\u6293\u5305\u5206\u6790"},{"location":"quickstart/#_4","text":"\u4e3a\u4e86\u7b80\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u7f16\u5199\u5de5\u4f5c\uff0cHttpRunner \u5b9e\u73b0\u4e86\u6d4b\u8bd5\u7528\u4f8b\u751f\u6210\u7684\u529f\u80fd\u3002 \u9996\u5148\uff0c\u9700\u8981\u5c06\u6293\u53d6\u5f97\u5230\u7684\u6570\u636e\u5305\u5bfc\u51fa\u4e3a HAR \u683c\u5f0f\u7684\u6587\u4ef6\uff0c\u5047\u8bbe\u5bfc\u51fa\u7684\u6587\u4ef6\u540d\u79f0\u4e3a demo-quickstart.har \u3002 \u7136\u540e\uff0c\u5728\u547d\u4ee4\u884c\u7ec8\u7aef\u4e2d\u8fd0\u884c\u5982\u4e0b\u547d\u4ee4\uff0c\u5373\u53ef\u5c06 demo-quickstart.har \u8f6c\u6362\u4e3a HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 $ har2case docs/data/demo-quickstart.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml \u4f7f\u7528 har2case \u8f6c\u6362\u811a\u672c\u65f6\u9ed8\u8ba4\u8f6c\u6362\u4e3a JSON \u683c\u5f0f\uff0c\u52a0\u4e0a -2y \u53c2\u6570\u540e\u8f6c\u6362\u4e3a YAML \u683c\u5f0f\u3002\u4e24\u79cd\u683c\u5f0f\u5b8c\u5168\u7b49\u4ef7\uff0cYAML \u683c\u5f0f\u66f4\u7b80\u6d01\uff0cJSON \u683c\u5f0f\u652f\u6301\u7684\u5de5\u5177\u66f4\u4e30\u5bcc\uff0c\u5927\u5bb6\u53ef\u6839\u636e\u4e2a\u4eba\u559c\u597d\u8fdb\u884c\u9009\u62e9\u3002\u5173\u4e8e har2case \u7684\u8be6\u7ec6\u4f7f\u7528\u8bf4\u660e\uff0c\u8bf7\u67e5\u770b \u300a\u5f55\u5236\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u300b \u3002 \u7ecf\u8fc7\u8f6c\u6362\uff0c\u5728\u6e90 demo-quickstart.har \u6587\u4ef6\u7684\u540c\u7ea7\u76ee\u5f55\u4e0b\u751f\u6210\u4e86\u76f8\u540c\u6587\u4ef6\u540d\u79f0\u7684 YAML \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6 demo-quickstart.yml \uff0c\u5176\u5185\u5bb9\u5982\u4e0b\uff1a - config : name : testcase description variables : {} - test : name : /api/get-token request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 app_version : 2.8.6 device_sn : FwgRiO7CNA50DSU os_platform : ios json : sign : 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 method : POST url : http://127.0.0.1:5000/api/get-token validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] - eq : [ content.token , baNLX1zhFYP11Seb ] - test : name : /api/users/1000 request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 device_sn : FwgRiO7CNA50DSU token : baNLX1zhFYP11Seb json : name : user1 password : '123456' method : POST url : http://127.0.0.1:5000/api/users/1000 validate : - eq : [ status_code , 201 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] - eq : [ content.msg , user created successfully. ] \u73b0\u5728\u6211\u4eec\u53ea\u9700\u8981\u77e5\u9053\u5982\u4e0b\u51e0\u70b9\uff1a \u6bcf\u4e2a YAML/JSON \u6587\u4ef6\u5bf9\u5e94\u4e00\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09 \u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u4e3a\u4e00\u4e2a list of dict \u7ed3\u6784\uff0c\u5176\u4e2d\u53ef\u80fd\u5305\u542b\u5168\u5c40\u914d\u7f6e\u9879\uff08config\uff09\u548c\u82e5\u5e72\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09 config \u4e3a\u5168\u5c40\u914d\u7f6e\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b test \u5bf9\u5e94\u5355\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u4f5c\u7528\u57df\u4ec5\u9650\u4e8e\u672c\u8eab \u5982\u4e0a\u4fbf\u662f HttpRunner \u6d4b\u8bd5\u7528\u4f8b\u7684\u57fa\u672c\u7ed3\u6784\u3002 \u5173\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u7684\u66f4\u591a\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u63cf\u8ff0\u300b \u3002","title":"\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b"},{"location":"quickstart/#_5","text":"\u6d4b\u8bd5\u7528\u4f8b\u5c31\u7eea\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5f00\u59cb\u8c03\u8bd5\u8fd0\u884c\u4e86\u3002 \u4e3a\u4e86\u6f14\u793a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8fed\u4ee3\u4f18\u5316\u8fc7\u7a0b\uff0c\u6211\u4eec\u5148\u5c06 demo-quickstart.json \u91cd\u547d\u540d\u4e3a demo-quickstart-0.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-0.yml \uff09\u3002 \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u547d\u4ee4\u4e3a hrun \uff0c\u540e\u9762\u76f4\u63a5\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\u5373\u53ef\u3002 $ hrun docs/data/demo-quickstart-0.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 9.26 ms, response_length: 46 bytes ERROR validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/get-token method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios'} json: {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'} verify: True ====== response details ====== status_code: 200 headers: {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'} body: '{\"success\": true, \"token\": \"tXGuSQgOCVXcltkz\"}' F /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000 ERROR validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) ERROR validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) ERROR validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/users/1000 method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'} json: {'name': 'user1', 'password': '123456'} verify: True ====== response details ====== status_code: 403 headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'} body: '{\"success\": false, \"msg\": \"Authorization failed!\"}' F ====================================================================== FAIL: test_0000_000 (httprunner.api.TestSequense) /api/get-token ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: content.token equals baNLX1zhFYP11Seb(str) ==> fail tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str) ====================================================================== FAIL: test_0001_000 (httprunner.api.TestSequense) /api/users/1000 ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ---------------------------------------------------------------------- Ran 2 tests in 0.026s FAILED (failures=2) INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513835.html \u975e\u5e38\u4e0d\u5e78\uff0c\u4e24\u4e2a\u63a5\u53e3\u7684\u6d4b\u8bd5\u7528\u4f8b\u5747\u8fd0\u884c\u5931\u8d25\u4e86\u3002","title":"\u9996\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b"},{"location":"quickstart/#_6","text":"\u4ece\u4e24\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u62a5\u9519\u4fe1\u606f\u548c\u5806\u6808\u4fe1\u606f\uff08Traceback\uff09\u53ef\u4ee5\u770b\u51fa\uff0c\u7b2c\u4e00\u4e2a\u6b65\u9aa4\u5931\u8d25\u7684\u539f\u56e0\u662f\u83b7\u53d6\u7684 token \u4e0e\u9884\u671f\u503c\u4e0d\u4e00\u81f4\uff0c\u7b2c\u4e8c\u4e2a\u6b65\u9aa4\u5931\u8d25\u7684\u539f\u56e0\u662f\u8bf7\u6c42\u6743\u9650\u6821\u9a8c\u5931\u8d25\uff08403\uff09\u3002 \u63a5\u4e0b\u6765\u6211\u4eec\u5c06\u9010\u6b65\u8fdb\u884c\u8fdb\u884c\u4f18\u5316\u3002","title":"\u4f18\u5316\u6d4b\u8bd5\u7528\u4f8b"},{"location":"quickstart/#_7","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c har2case \u751f\u6210\u7528\u4f8b\u65f6\uff0c\u82e5 HTTP \u8bf7\u6c42\u7684\u54cd\u5e94\u5185\u5bb9\u4e3a JSON \u683c\u5f0f\uff0c\u5219\u4f1a\u5c06\u7b2c\u4e00\u5c42\u7ea7\u4e2d\u7684\u6240\u6709 key-value \u8f6c\u6362\u4e3a validator\u3002 \u4f8b\u5982\u4e0a\u9762\u7684\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u751f\u6210\u7684 validator \u4e3a\uff1a \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]}, { \"eq\" : [ \"content.token\" , \"baNLX1zhFYP11Seb\" ]} ] \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\u5c31\u4f1a\u5bf9\u4e0a\u9762\u7684\u5404\u4e2a\u9879\u8fdb\u884c\u6821\u9a8c\u3002 \u95ee\u9898\u5728\u4e8e\uff0c\u8bf7\u6c42 /api/get-token \u63a5\u53e3\u65f6\uff0c\u6bcf\u6b21\u751f\u6210\u7684 token \u90fd\u4f1a\u662f\u4e0d\u540c\u7684\uff0c\u56e0\u6b64\u5c06\u751f\u6210\u7684 token \u4f5c\u4e3a\u6821\u9a8c\u9879\u7684\u8bdd\uff0c\u6821\u9a8c\u81ea\u7136\u5c31\u65e0\u6cd5\u901a\u8fc7\u4e86\u3002 \u6b63\u786e\u7684\u505a\u6cd5\u662f\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u7684 validate \u4e2d\u5e94\u8be5\u53bb\u6389\u8fd9\u7c7b\u52a8\u6001\u53d8\u5316\u7684\u503c\u3002 \u53bb\u9664\u8be5\u9879\u540e\uff0c\u5c06\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-1.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-1.yml \uff09\u3002 \u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u8fd0\u884c\u7ed3\u679c\u5982\u4e0b\uff1a $ hrun docs/data/demo-quickstart-1.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 6.61 ms, response_length: 46 bytes . /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 ERROR 403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000 ERROR validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) ERROR validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) ERROR validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ERROR ******************************** DETAILED REQUEST & RESPONSE ******************************** ====== request details ====== url: http://127.0.0.1:5000/api/users/1000 method: POST headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'} json: {'name': 'user1', 'password': '123456'} verify: True ====== response details ====== status_code: 403 headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:45:34 GMT'} body: '{\"success\": false, \"msg\": \"Authorization failed!\"}' F ====================================================================== FAIL: test_0001_000 (httprunner.api.TestSequense) /api/users/1000 ---------------------------------------------------------------------- Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 54, in test test_runner.run_test(test_dict) httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) During handling of the above exception, another exception occurred: Traceback (most recent call last): File \"/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py\", line 56, in test self.fail(str(ex)) AssertionError: validate: status_code equals 201(int) ==> fail 403(int) equals 201(int) validate: content.success equals True(bool) ==> fail False(bool) equals True(bool) validate: content.msg equals user created successfully.(str) ==> fail Authorization failed!(str) equals user created successfully.(str) ---------------------------------------------------------------------- Ran 2 tests in 0.018s FAILED (failures=1) INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513934.html \u7ecf\u8fc7\u4fee\u6539\uff0c\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5df2\u7ecf\u8fd0\u884c\u6210\u529f\u4e86\uff0c\u7b2c\u4e8c\u4e2a\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u5931\u8d25\uff08403\uff09\uff0c\u8fd8\u662f\u56e0\u4e3a\u6743\u9650\u6821\u9a8c\u7684\u539f\u56e0\u3002","title":"\u8c03\u6574\u6821\u9a8c\u5668"},{"location":"quickstart/#_8","text":"\u6211\u4eec\u7ee7\u7eed\u67e5\u770b demo-quickstart-1.json \uff0c\u4f1a\u53d1\u73b0\u7b2c\u4e8c\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u8bf7\u6c42 headers \u4e2d\u7684 token \u4ecd\u7136\u662f\u786c\u7f16\u7801\u7684\uff0c\u5373\u6293\u5305\u65f6\u83b7\u53d6\u5230\u7684\u503c\u3002\u5728\u6211\u4eec\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\u8fd9\u4e2a token \u5df2\u7ecf\u5931\u6548\u4e86\uff0c\u6240\u4ee5\u4f1a\u51fa\u73b0 403 \u6743\u9650\u6821\u9a8c\u5931\u8d25\u7684\u95ee\u9898\u3002 \u6b63\u786e\u7684\u505a\u6cd5\u662f\uff0c\u6211\u4eec\u5e94\u8be5\u5728\u6bcf\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u65f6\u5019\uff0c\u5148\u52a8\u6001\u83b7\u53d6\u5230\u7b2c\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u7684 token\uff0c\u7136\u540e\u5728\u540e\u7eed\u6d4b\u8bd5\u6b65\u9aa4\u7684\u8bf7\u6c42\u4e2d\u4f7f\u7528\u524d\u9762\u83b7\u53d6\u5230\u7684 token\u3002 \u5728 HttpRunner \u4e2d\uff0c\u652f\u6301\u53c2\u6570\u63d0\u53d6\uff08 extract \uff09\u548c\u53c2\u6570\u5f15\u7528\u7684\u529f\u80fd\uff08 $var \uff09\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u4e2d\uff0c\u82e5\u9700\u8981\u4ece\u54cd\u5e94\u7ed3\u679c\u4e2d\u63d0\u53d6\u53c2\u6570\uff0c\u5219\u53ef\u4f7f\u7528 extract \u5173\u952e\u5b57\u3002extract \u7684\u5217\u8868\u4e2d\u53ef\u6307\u5b9a\u4e00\u4e2a\u6216\u591a\u4e2a\u9700\u8981\u63d0\u53d6\u7684\u53c2\u6570\u3002 \u5728\u63d0\u53d6\u53c2\u6570\u65f6\uff0c\u5f53 HTTP \u7684\u8bf7\u6c42\u54cd\u5e94\u7ed3\u679c\u4e3a JSON \u683c\u5f0f\uff0c\u5219\u53ef\u4ee5\u91c7\u7528 . \u8fd0\u7b97\u7b26\u7684\u65b9\u5f0f\uff0c\u9010\u7ea7\u5f80\u4e0b\u83b7\u53d6\u5230\u53c2\u6570\u503c\uff1b\u54cd\u5e94\u7ed3\u679c\u7684\u6574\u4f53\u5185\u5bb9\u5f15\u7528\u65b9\u5f0f\u4e3a content \u6216\u8005 body\u3002 \u4f8b\u5982\uff0c\u7b2c\u4e00\u4e2a\u63a5\u53e3 /api/get-token \u7684\u54cd\u5e94\u7ed3\u679c\u4e3a\uff1a { \"success\" : true , \"token\" : \"ZQkYhbaQ6q8UFFNE\" } \u90a3\u4e48\u8981\u83b7\u53d6\u5230 token \u53c2\u6570\uff0c\u5c31\u53ef\u4ee5\u4f7f\u7528 content.token \u7684\u65b9\u5f0f\uff1b\u5177\u4f53\u7684\u5199\u6cd5\u5982\u4e0b\uff1a \"extract\" : [ { \"token\" : \"content.token\" } ] \u5176\u4e2d\uff0ctoken \u4f5c\u4e3a\u63d0\u53d6\u540e\u7684\u53c2\u6570\u540d\u79f0\uff0c\u53ef\u4ee5\u5728\u540e\u7eed\u4f7f\u7528 $token \u8fdb\u884c\u5f15\u7528\u3002 \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"$token\" , \"Content-Type\" : \"application/json\" } \u4fee\u6539\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-2.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-2.yml \uff09\u3002 \u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u8fd0\u884c\u7ed3\u679c\u5982\u4e0b\uff1a $ hrun docs/data/demo-quickstart-2.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.32 ms, response_length: 46 bytes . /api/users/1000 INFO POST http://127.0.0.1:5000/api/users/1000 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.019s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548514191.html \u7ecf\u8fc7\u4fee\u6539\uff0c\u7b2c\u4e8c\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e5f\u8fd0\u884c\u6210\u529f\u4e86\u3002","title":"\u53c2\u6570\u5173\u8054"},{"location":"quickstart/#base_url","text":"\u867d\u7136\u6d4b\u8bd5\u6b65\u9aa4\u8fd0\u884c\u90fd\u6210\u529f\u4e86\uff0c\u4f46\u662f\u4ecd\u7136\u6709\u7ee7\u7eed\u4f18\u5316\u7684\u5730\u65b9\u3002 \u7ee7\u7eed\u67e5\u770b demo-quickstart-2.json \uff0c\u6211\u4eec\u4f1a\u53d1\u73b0\u5728\u6bcf\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u7684 URL \u4e2d\uff0c\u90fd\u91c7\u7528\u7684\u662f\u5b8c\u6574\u7684\u63cf\u8ff0\uff08host+path\uff09\uff0c\u4f46\u5927\u591a\u6570\u60c5\u51b5\u4e0b\u540c\u4e00\u4e2a\u7528\u4f8b\u4e2d\u7684 host \u90fd\u662f\u76f8\u540c\u7684\uff0c\u533a\u522b\u4ec5\u5728\u4e8e path \u90e8\u5206\u3002 \u56e0\u6b64\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09 URL \u7684 base_url \u62bd\u53d6\u51fa\u6765\uff0c\u653e\u5230\u5168\u5c40\u914d\u7f6e\u6a21\u5757\uff08config\uff09\u4e2d\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u7684 URL \u53ea\u4fdd\u7559 PATH \u90e8\u5206\u3002 - config : name : testcase description base_url : http://127.0.0.1:5000 - test : name : get token request : url : /api/get-token \u8c03\u6574\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-3.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-3.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002","title":"base_url"},{"location":"quickstart/#_9","text":"\u7ee7\u7eed\u67e5\u770b demo-quickstart-3.json \uff0c\u6211\u4eec\u4f1a\u53d1\u73b0\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b58\u5728\u8f83\u591a\u786c\u7f16\u7801\u7684\u53c2\u6570\uff0c\u4f8b\u5982 app_version\u3001device_sn\u3001os_platform\u3001user_id \u7b49\u3002 \u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u6211\u4eec\u53ef\u4ee5\u4e0d\u7528\u4fee\u6539\u8fd9\u4e9b\u786c\u7f16\u7801\u7684\u53c2\u6570\uff0c\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u80fd\u6b63\u5e38\u8fd0\u884c\u3002\u4f46\u662f\u4e3a\u4e86\u66f4\u597d\u5730\u7ef4\u62a4\u6d4b\u8bd5\u7528\u4f8b\uff0c\u4f8b\u5982\u540c\u4e00\u4e2a\u53c2\u6570\u503c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u51fa\u73b0\u591a\u6b21\uff0c\u90a3\u4e48\u6bd4\u8f83\u597d\u7684\u505a\u6cd5\u662f\uff0c\u5c06\u8fd9\u4e9b\u53c2\u6570\u5b9a\u4e49\u4e3a\u53d8\u91cf\uff0c\u7136\u540e\u5728\u9700\u8981\u53c2\u6570\u7684\u5730\u65b9\u8fdb\u884c\u5f15\u7528\u3002 \u5728 HttpRunner \u4e2d\uff0c\u652f\u6301\u53d8\u91cf\u7533\u660e\uff08 variables \uff09\u548c\u5f15\u7528\uff08 $var \uff09\u7684\u673a\u5236\u3002\u5728 config \u548c test \u4e2d\u5747\u53ef\u4ee5\u901a\u8fc7 variables \u5173\u952e\u5b57\u5b9a\u4e49\u53d8\u91cf\uff0c\u7136\u540e\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u53ef\u4ee5\u901a\u8fc7 $ + \u53d8\u91cf\u540d\u79f0 \u7684\u65b9\u5f0f\u5f15\u7528\u53d8\u91cf\u3002\u533a\u522b\u5728\u4e8e\uff0c\u5728 config \u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u4e3a\u5168\u5c40\u7684\uff0c\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u7684\u6240\u6709\u5730\u65b9\u5747\u53ef\u4ee5\u5f15\u7528\uff1b\u5728 test \u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u4f5c\u7528\u57df\u4ec5\u5c40\u9650\u4e8e\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u3002 \u5bf9\u4e0a\u8ff0\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u786c\u7f16\u7801\u7684\u53c2\u6570\u8fdb\u884c\u53d8\u91cf\u7533\u660e\u548c\u5f15\u7528\u8c03\u6574\u540e\uff0c\u65b0\u7684\u6d4b\u8bd5\u7528\u4f8b\u53e6\u5b58\u4e3a demo-quickstart-4.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-4.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002","title":"\u53d8\u91cf\u7684\u7533\u660e\u548c\u5f15\u7528"},{"location":"quickstart/#_10","text":"\u67e5\u770b demo-quickstart-4.json \u53ef\u4ee5\u770b\u51fa\uff0c\u4e24\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u90fd\u5b9a\u4e49\u4e86 device_sn\u3002\u9488\u5bf9\u8fd9\u7c7b\u516c\u5171\u7684\u53c2\u6570\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06\u5176\u7edf\u4e00\u5b9a\u4e49\u5728 config \u7684 variables \u4e2d\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5c31\u4e0d\u7528\u518d\u91cd\u590d\u5b9a\u4e49\u3002 - config : name : testcase description base_url : http://127.0.0.1:5000 variables : device_sn : FwgRiO7CNA50DSU \u8c03\u6574\u540e\u7684\u6d4b\u8bd5\u7528\u4f8b\u89c1 demo-quickstart-5.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-5.yml \uff09\u3002","title":"\u62bd\u53d6\u516c\u5171\u53d8\u91cf"},{"location":"quickstart/#_11","text":"\u5728 demo-quickstart-5.yml \u4e2d\uff0c\u53c2\u6570 device_sn \u4ee3\u8868\u7684\u662f\u8bbe\u5907\u7684 SN \u7f16\u7801\uff0c\u867d\u7136\u91c7\u7528\u786c\u7f16\u7801\u7684\u65b9\u5f0f\u6682\u65f6\u4e0d\u5f71\u54cd\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\uff0c\u4f46\u8fd9\u4e0e\u771f\u5b9e\u7684\u7528\u6237\u573a\u666f\u4e0d\u5927\u76f8\u7b26\u3002 \u5047\u8bbe device_sn \u7684\u683c\u5f0f\u4e3a 15 \u957f\u5ea6\u7684\u5b57\u7b26\u4e32\uff0c\u90a3\u4e48\u6211\u4eec\u5c31\u53ef\u4ee5\u5728\u6bcf\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u7684\u65f6\u5019\uff0c\u9488\u5bf9 device_sn \u751f\u6210\u4e00\u4e2a 15 \u4f4d\u957f\u5ea6\u7684\u968f\u673a\u5b57\u7b26\u4e32\u3002\b\u4e0e\u6b64\u540c\u65f6\uff0csign \u5b57\u6bb5\u662f\u6839\u636e headers \u4e2d\u7684\u5404\u4e2a\u5b57\u6bb5\u62fc\u63a5\u540e\u751f\u6210\u5f97\u5230\u7684 MD5 \u503c\uff0c\u56e0\u6b64\u5728 device_sn \u53d8\u52a8\u540e\uff0csign \u4e5f\u5e94\u8be5\u91cd\u65b0\u8fdb\u884c\u8ba1\u7b97\uff0c\u5426\u5219\u5c31\u4f1a\u518d\u6b21\u51fa\u73b0\u7b7e\u540d\u6821\u9a8c\u5931\u8d25\u7684\u95ee\u9898\u3002 \u7136\u800c\uff0cHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u90fd\u662f\u91c7\u7528 YAML/JSON \u683c\u5f0f\u8fdb\u884c\u63cf\u8ff0\u7684\uff0c\u5728\u6587\u672c\u683c\u5f0f\u4e2d\u5982\u4f55\u6267\u884c\u4ee3\u7801\u8fd0\u7b97\u5462\uff1f HttpRunner \u7684\u5b9e\u73b0\u65b9\u5f0f\u4e3a\uff0c\u652f\u6301\u70ed\u52a0\u8f7d\u7684\u63d2\u4ef6\u673a\u5236\uff08 debugtalk.py \uff09\uff0c\u53ef\u4ee5\u5728 YAML/JSON \u4e2d\u8c03\u7528 Python \u51fd\u6570\u3002 \u5177\u4f53\u5730\u505a\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u540c\u7ea7\u6216\u5176\u7236\u7ea7\u76ee\u5f55\u4e2d\u521b\u5efa\u4e00\u4e2a debugtalk.py \u6587\u4ef6\uff0c\u7136\u540e\u5728\u5176\u4e2d\u5b9a\u4e49\u76f8\u5173\u7684\u51fd\u6570\u548c\u53d8\u91cf\u3002 \u4f8b\u5982\uff0c\u9488\u5bf9 device_sn \u7684\u968f\u673a\u5b57\u7b26\u4e32\u751f\u6210\u529f\u80fd\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u4e00\u4e2a gen_random_string \u51fd\u6570\uff1b\u9488\u5bf9 sign \u7684\u7b7e\u540d\u7b97\u6cd5\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9a\u4e49\u4e00\u4e2a get_sign \u51fd\u6570\u3002 import hashlib import hmac import random import string SECRET_KEY = \"DebugTalk\" def gen_random_string ( str_len ): random_char_list = [] for _ in range ( str_len ): random_char = random . choice ( string . ascii_letters + string . digits ) random_char_list . append ( random_char ) random_string = '' . join ( random_char_list ) return random_string def get_sign ( * args ): content = '' . join ( args ) . encode ( 'ascii' ) sign_key = SECRET_KEY . encode ( 'ascii' ) sign = hmac . new ( sign_key , content , hashlib . sha1 ) . hexdigest () return sign \u7136\u540e\uff0c\u6211\u4eec\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u5bf9\u5b9a\u4e49\u7684\u51fd\u6570\u8fdb\u884c\u8c03\u7528\uff0c\u5bf9\u5b9a\u4e49\u7684\u53d8\u91cf\u8fdb\u884c\u5f15\u7528\u4e86\u3002\u5f15\u7528\u53d8\u91cf\u7684\u65b9\u5f0f\u4ecd\u7136\u4e0e\u524d\u9762\u8bb2\u7684\u4e00\u6837\uff0c\u91c7\u7528 $ + \u53d8\u91cf\u540d\u79f0 \u7684\u65b9\u5f0f\uff1b\u8c03\u7528\u51fd\u6570\u7684\u65b9\u5f0f\u4e3a ${func($var)} \u3002 \u4f8b\u5982\uff0c\u751f\u6210 15 \u4f4d\u957f\u5ea6\u7684\u968f\u673a\u5b57\u7b26\u4e32\u5e76\u8d4b\u503c\u7ed9 device_sn \u7684\u4ee3\u7801\u4e3a\uff1a \"variables\" : [ { \"device_sn\" : \"${gen_random_string(15)}\" } ] \u4f7f\u7528 $user_agent\u3001$device_sn\u3001$os_platform\u3001$app_version \u6839\u636e\u7b7e\u540d\u7b97\u6cd5\u751f\u6210 sign \u503c\u7684\u4ee3\u7801\u4e3a\uff1a \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" } \u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u4e0a\u8ff0\u8c03\u6574\u540e\uff0c\u53e6\u5b58\u4e3a demo-quickstart-6.json \uff08\u5bf9\u5e94\u7684 YAML \u683c\u5f0f\uff1a demo-quickstart-6.yml \uff09\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6240\u6709\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4ecd\u7136\u8fd0\u884c\u6210\u529f\u3002","title":"\u5b9e\u73b0\u52a8\u6001\u8fd0\u7b97\u903b\u8f91"},{"location":"quickstart/#_12","text":"\u8bf7\u786e\u4fdd\u4f60\u4f7f\u7528\u7684 HttpRunner \u7248\u672c\u53f7\u4e0d\u4f4e\u4e8e 2.0.0 \u5728 demo-quickstart-6.yml \u4e2d\uff0cuser_id \u4ecd\u7136\u662f\u5199\u6b7b\u7684\u503c\uff0c\u5047\u5982\u6211\u4eec\u9700\u8981\u521b\u5efa user_id \u4e3a 1001\uff5e1004 \u7684\u7528\u6237\uff0c\u90a3\u6211\u4eec\u53ea\u80fd\u4e0d\u65ad\u5730\u53bb\u4fee\u6539 user_id\uff0c\u7136\u540e\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u91cd\u590d\u64cd\u4f5c 4 \u6b21\uff1f\u6216\u8005\u6211\u4eec\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u5c06\u521b\u5efa\u7528\u6237\u7684 test \u590d\u5236 4 \u4efd\uff0c\u7136\u540e\u5728\u6bcf\u4e00\u4efd\u91cc\u9762\u5206\u522b\u4f7f\u7528\u4e0d\u540c\u7684 user_id \uff1f \u5f88\u663e\u7136\uff0c\u4e0d\u7ba1\u662f\u91c7\u7528\u4e0a\u8ff0\u54ea\u79cd\u65b9\u5f0f\uff0c\u90fd\u4f1a\u5f88\u7e41\u7410\uff0c\u5e76\u4e14\u4e5f\u65e0\u6cd5\u5e94\u5bf9\u7075\u6d3b\u591a\u53d8\u7684\u6d4b\u8bd5\u9700\u6c42\u3002 \u9488\u5bf9\u8fd9\u7c7b\u9700\u6c42\uff0cHttpRunner \u652f\u6301\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u7684\u529f\u80fd\u3002 \u5728 HttpRunner \u4e2d\uff0c\u82e5\u8981\u91c7\u7528\u6570\u636e\u9a71\u52a8\u7684\u65b9\u5f0f\u6765\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u9700\u8981\u521b\u5efa\u4e00\u4e2a\u6587\u4ef6\uff0c\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u5f15\u7528\uff0c\u5e76\u4f7f\u7528 parameters \u5173\u952e\u5b57\u5b9a\u4e49\u53c2\u6570\u5e76\u6307\u5b9a\u6570\u636e\u6e90\u53d6\u503c\u65b9\u5f0f\u3002 \u4f8b\u5982\uff0c\u6211\u4eec\u9700\u8981\u5728\u521b\u5efa\u7528\u6237\u7684\u63a5\u53e3\u4e2d\u5bf9 user_id \u8fdb\u884c\u53c2\u6570\u5316\uff0c\u53c2\u6570\u5316\u5217\u8868\u4e3a 1001\uff5e1004\uff0c\u5e76\u4e14\u53d6\u503c\u65b9\u5f0f\u4e3a\u987a\u5e8f\u53d6\u503c\uff0c\u90a3\u4e48\u6700\u7b80\u5355\u7684\u63cf\u8ff0\u65b9\u5f0f\u5c31\u662f\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\u3002\u5177\u4f53\u7684\u7f16\u5199\u65b9\u5f0f\u4e3a\uff0c\u65b0\u5efa\u4e00\u4e2a\u6d4b\u8bd5\u573a\u666f\u6587\u4ef6 demo-quickstart-7.yml \uff08\u5bf9\u5e94\u7684 JSON \u683c\u5f0f\uff1a demo-quickstart-7.json \uff09\uff0c\u5185\u5bb9\u5982\u4e0b\u6240\u793a\uff1a config : name : testcase description testcases : create user : testcase : demo-quickstart-6.yml parameters : user_id : [ 1001 , 1002 , 1003 , 1004 ] \u4ec5\u9700\u5982\u4e0a\u914d\u7f6e\uff0c\u9488\u5bf9 user_id \u7684\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u5c31\u5b8c\u6210\u4e86\u3002 \u91cd\u542f flask \u5e94\u7528\u670d\u52a1\u540e\u518d\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u60c5\u51b5\u5982\u4e0b\u6240\u793a\uff1a \u70b9\u51fb\u67e5\u770b\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-quickstart-7.json INFO Start to run testcase: create user 1001 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes . /api/users/1001 INFO POST http://127.0.0.1:5000/api/users/1001 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.021s OK INFO Start to run testcase: create user 1002 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes . /api/users/1002 INFO POST http://127.0.0.1:5000/api/users/1002 INFO status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK INFO Start to run testcase: create user 1003 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes . /api/users/1003 INFO POST http://127.0.0.1:5000/api/users/1003 INFO status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.011s OK INFO Start to run testcase: create user 1004 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes . /api/users/1004 INFO POST http://127.0.0.1:5000/api/users/1004 INFO status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.016s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html \u53ef\u4ee5\u770b\u51fa\uff0c\u6d4b\u8bd5\u7528\u4f8b\u603b\u5171\u8fd0\u884c\u4e86 4 \u6b21\uff0c\u5e76\u4e14\u6bcf\u6b21\u8fd0\u884c\u65f6\u90fd\u662f\u91c7\u7528\u7684\u4e0d\u540c user_id\u3002 \u5173\u4e8e\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u8fd9\u91cc\u53ea\u63cf\u8ff0\u4e86\u6700\u7b80\u5355\u7684\u573a\u666f\u548c\u4f7f\u7528\u65b9\u5f0f\uff0c\u5982\u9700\u4e86\u89e3\u66f4\u591a\uff0c\u8bf7\u8fdb\u4e00\u6b65\u9605\u8bfb \u300a\u6570\u636e\u9a71\u52a8\u4f7f\u7528\u624b\u518c\u300b \u3002","title":"\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8"},{"location":"quickstart/#_13","text":"\u5728\u6bcf\u6b21\u4f7f\u7528 hrun \u547d\u4ee4\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u540e\uff0c\u5747\u4f1a\u751f\u6210\u4e00\u4efd HTML \u683c\u5f0f\u7684\u6d4b\u8bd5\u62a5\u544a\u3002\u62a5\u544a\u6587\u4ef6\u4f4d\u4e8e reports \u76ee\u5f55\u4e0b\uff0c\u6587\u4ef6\u540d\u79f0\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5f00\u59cb\u8fd0\u884c\u65f6\u95f4\u3002 \u4f8b\u5982\uff0c\u5728\u8fd0\u884c\u5b8c demo-quickstart-1.json \u540e\uff0c\u5c06\u751f\u6210\u5982\u4e0b\u5f62\u5f0f\u7684\u6d4b\u8bd5\u62a5\u544a\uff1a \u5173\u4e8e\u6d4b\u8bd5\u62a5\u544a\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u300a\u6d4b\u8bd5\u62a5\u544a\u300b \u90e8\u5206\u3002","title":"\u67e5\u770b\u6d4b\u8bd5\u62a5\u544a"},{"location":"quickstart/#_14","text":"\u5230\u6b64\u4e3a\u6b62\uff0cHttpRunner \u7684\u6838\u5fc3\u529f\u80fd\u5c31\u4ecb\u7ecd\u5b8c\u4e86\uff0c\u638c\u63e1\u672c\u6587\u4e2d\u7684\u529f\u80fd\u7279\u6027\uff0c\u8db3\u4ee5\u5e2e\u52a9\u4f60\u5e94\u5bf9\u65e5\u5e38\u9879\u76ee\u5de5\u4f5c\u4e2d\u81f3\u5c11 80% \u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u9700\u6c42\u3002 \u5f53\u7136\uff0cHttpRunner \u4e0d\u6b62\u4e8e\u6b64\uff0c\u5982\u9700\u6316\u6398 HttpRunner \u7684\u66f4\u591a\u7279\u6027\uff0c\u5b9e\u73b0\u66f4\u590d\u6742\u573a\u666f\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u9700\u6c42\uff0c\u53ef\u7ee7\u7eed\u9605\u8bfb\u540e\u7eed\u6587\u6863\u3002","title":"\u603b\u7ed3"},{"location":"related-docs/","text":"\u5f00\u53d1\u7b14\u8bb0 \u00b6 DebugTalk \u6f14\u8bb2 \u00b6 MTSC 2018 : \u300a\u5927\u7586\u4e92\u8054\u7f51\u7684\u4e00\u7ad9\u5f0f\u81ea\u52a8\u5316\u6d4b\u8bd5\u89e3\u51b3\u65b9\u6848\uff08\u57fa\u4e8eHttpRunner\uff09\u300b PyCon China 2018 : \u300a\u501f\u52a9 Python \u5f00\u6e90\u751f\u6001\u6253\u9020\u4f01\u4e1a\u7ea7\u81ea\u52a8\u5316\u6d4b\u8bd5\u6846\u67b6\uff08HttpRunner\uff09\u300b MTSC 2019 : \u300aHttpRunner 2.0 \u6280\u672f\u67b6\u6784\u4e0e\u63a5\u53e3\u6d4b\u8bd5\u5e94\u7528\u300b","title":"\u76f8\u5173\u8d44\u6599"},{"location":"related-docs/#_1","text":"DebugTalk","title":"\u5f00\u53d1\u7b14\u8bb0"},{"location":"related-docs/#_2","text":"MTSC 2018 : \u300a\u5927\u7586\u4e92\u8054\u7f51\u7684\u4e00\u7ad9\u5f0f\u81ea\u52a8\u5316\u6d4b\u8bd5\u89e3\u51b3\u65b9\u6848\uff08\u57fa\u4e8eHttpRunner\uff09\u300b PyCon China 2018 : \u300a\u501f\u52a9 Python \u5f00\u6e90\u751f\u6001\u6253\u9020\u4f01\u4e1a\u7ea7\u81ea\u52a8\u5316\u6d4b\u8bd5\u6846\u67b6\uff08HttpRunner\uff09\u300b MTSC 2019 : \u300aHttpRunner 2.0 \u6280\u672f\u67b6\u6784\u4e0e\u63a5\u53e3\u6d4b\u8bd5\u5e94\u7528\u300b","title":"\u6f14\u8bb2"},{"location":"sponsors/","text":"\u8d5e\u52a9\u5546 \u00b6 \u611f\u8c22\u5404\u4f4d\u5bf9 HttpRunner \u7684\u8d5e\u52a9\u652f\u6301\uff01 \u91d1\u724c\u8d5e\u52a9\u5546\uff08Gold Sponsor\uff09 \u00b6 \u970d\u683c\u6c83\u5179\u6d4b\u8bd5\u5b66\u9662 \u662f\u7531\u6d4b\u5427\uff08\u5317\u4eac\uff09\u79d1\u6280\u6709\u9650\u516c\u53f8\u4e0e\u77e5\u540d\u8f6f\u4ef6\u6d4b\u8bd5\u793e\u533a TesterHome \u5408\u4f5c\u7684\u9ad8\u7aef\u6559\u80b2\u54c1\u724c\u3002\u7531 BAT \u4e00\u7ebf \u6d4b\u8bd5\u5927\u5496\u6267\u6559 \uff0c\u63d0\u4f9b \u5b9e\u6218\u9a71\u52a8 \u7684\u63a5\u53e3\u81ea\u52a8\u5316\u6d4b\u8bd5\u3001\u79fb\u52a8\u81ea\u52a8\u5316\u6d4b\u8bd5\u3001\u6027\u80fd\u6d4b\u8bd5\u3001\u6301\u7eed\u96c6\u6210\u4e0e DevOps \u7b49\u6280\u672f\u57f9\u8bad\uff0c\u4ee5\u53ca\u6d4b\u8bd5\u5f00\u53d1\u4f18\u79c0\u4eba\u624d\u5185\u63a8\u670d\u52a1\u3002 \u70b9\u51fb\u5b66\u4e60! \u970d\u683c\u6c83\u5179\u6d4b\u8bd5\u5b66\u9662\u662f HttpRunner \u7684\u9996\u5bb6\u91d1\u724c\u8d5e\u52a9\u5546\u3002 \u6210\u4e3a\u8d5e\u52a9\u5546 \u00b6 \u5982\u679c\u4f60\u6240\u5728\u7684\u516c\u53f8\u6216\u4e2a\u4eba\u4e5f\u60f3\u5bf9 HttpRunner \u8fdb\u884c\u8d5e\u52a9\uff0c\u53ef\u53c2\u8003\u5982\u4e0b\u65b9\u6848\uff0c\u5177\u4f53\u53ef\u8054\u7cfb \u9879\u76ee\u4f5c\u8005 \u3002 \u7b49\u7ea7 \u91d1\u724c\u8d5e\u52a9\u5546 \uff08Gold Sponsor\uff09 \u94f6\u724c\u8d5e\u52a9\u5546 \uff08Silver Sponsor\uff09 \u4e2a\u4eba\u8d5e\u8d4f \u91d1\u989d \uffe510000/\u5e74 \uffe55000/\u5e74 \u4efb\u610f \u6743\u76ca \u516c\u53f8 logo\uff08\u5927\uff09\u548c\u94fe\u63a5\u5c55\u793a\u5728 README.md 150 \u5b57\u7684\u5ba3\u4f20\u6587\u6848 \u516c\u53f8 logo\uff08\u4e2d\uff09\u548c\u94fe\u63a5\u5c55\u793a\u5728 README.md 50 \u5b57\u7684\u5ba3\u4f20\u6587\u6848 \u4e2a\u4eba ID \u548c\u94fe\u63a5\u5c55\u793a\u5728 sponsors.md","title":"\u8d5e\u52a9\u5546"},{"location":"sponsors/#_1","text":"\u611f\u8c22\u5404\u4f4d\u5bf9 HttpRunner \u7684\u8d5e\u52a9\u652f\u6301\uff01","title":"\u8d5e\u52a9\u5546"},{"location":"sponsors/#gold-sponsor","text":"\u970d\u683c\u6c83\u5179\u6d4b\u8bd5\u5b66\u9662 \u662f\u7531\u6d4b\u5427\uff08\u5317\u4eac\uff09\u79d1\u6280\u6709\u9650\u516c\u53f8\u4e0e\u77e5\u540d\u8f6f\u4ef6\u6d4b\u8bd5\u793e\u533a TesterHome \u5408\u4f5c\u7684\u9ad8\u7aef\u6559\u80b2\u54c1\u724c\u3002\u7531 BAT \u4e00\u7ebf \u6d4b\u8bd5\u5927\u5496\u6267\u6559 \uff0c\u63d0\u4f9b \u5b9e\u6218\u9a71\u52a8 \u7684\u63a5\u53e3\u81ea\u52a8\u5316\u6d4b\u8bd5\u3001\u79fb\u52a8\u81ea\u52a8\u5316\u6d4b\u8bd5\u3001\u6027\u80fd\u6d4b\u8bd5\u3001\u6301\u7eed\u96c6\u6210\u4e0e DevOps \u7b49\u6280\u672f\u57f9\u8bad\uff0c\u4ee5\u53ca\u6d4b\u8bd5\u5f00\u53d1\u4f18\u79c0\u4eba\u624d\u5185\u63a8\u670d\u52a1\u3002 \u70b9\u51fb\u5b66\u4e60! \u970d\u683c\u6c83\u5179\u6d4b\u8bd5\u5b66\u9662\u662f HttpRunner \u7684\u9996\u5bb6\u91d1\u724c\u8d5e\u52a9\u5546\u3002","title":"\u91d1\u724c\u8d5e\u52a9\u5546\uff08Gold Sponsor\uff09"},{"location":"sponsors/#_2","text":"\u5982\u679c\u4f60\u6240\u5728\u7684\u516c\u53f8\u6216\u4e2a\u4eba\u4e5f\u60f3\u5bf9 HttpRunner \u8fdb\u884c\u8d5e\u52a9\uff0c\u53ef\u53c2\u8003\u5982\u4e0b\u65b9\u6848\uff0c\u5177\u4f53\u53ef\u8054\u7cfb \u9879\u76ee\u4f5c\u8005 \u3002 \u7b49\u7ea7 \u91d1\u724c\u8d5e\u52a9\u5546 \uff08Gold Sponsor\uff09 \u94f6\u724c\u8d5e\u52a9\u5546 \uff08Silver Sponsor\uff09 \u4e2a\u4eba\u8d5e\u8d4f \u91d1\u989d \uffe510000/\u5e74 \uffe55000/\u5e74 \u4efb\u610f \u6743\u76ca \u516c\u53f8 logo\uff08\u5927\uff09\u548c\u94fe\u63a5\u5c55\u793a\u5728 README.md 150 \u5b57\u7684\u5ba3\u4f20\u6587\u6848 \u516c\u53f8 logo\uff08\u4e2d\uff09\u548c\u94fe\u63a5\u5c55\u793a\u5728 README.md 50 \u5b57\u7684\u5ba3\u4f20\u6587\u6848 \u4e2a\u4eba ID \u548c\u94fe\u63a5\u5c55\u793a\u5728 sponsors.md","title":"\u6210\u4e3a\u8d5e\u52a9\u5546"},{"location":"concept/nominal/","text":"\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09 \u00b6 \u4ece 2.0 \u7248\u672c\u5f00\u59cb\uff0cHttpRunner \u5f00\u59cb\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u7684\u5b9a\u4e49\u8fdb\u884c\u8fdb\u4e00\u6b65\u7684\u660e\u786e\uff0c\u53c2\u8003 wiki \u4e0a\u7684\u63cf\u8ff0\u3002 A test case is a specification of the inputs, execution conditions, testing procedure, and expected results that define a single test to be executed to achieve a particular software testing objective, such as to exercise a particular program path or to verify compliance with a specific requirement. \u6982\u62ec\u4e0b\u6765\uff0c\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5e94\u8be5\u662f\u4e3a\u4e86\u6d4b\u8bd5\u67d0\u4e2a\u7279\u5b9a\u7684\u529f\u80fd\u903b\u8f91\u800c\u7cbe\u5fc3\u8bbe\u8ba1\u7684\uff0c\u5e76\u4e14\u81f3\u5c11\u5305\u542b\u5982\u4e0b\u51e0\u70b9\uff1a \u660e\u786e\u7684\u6d4b\u8bd5\u76ee\u7684\uff08achieve a particular software testing objective\uff09 \u660e\u786e\u7684\u8f93\u5165\uff08inputs\uff09 \u660e\u786e\u7684\u8fd0\u884c\u73af\u5883\uff08execution conditions\uff09 \u660e\u786e\u7684\u6d4b\u8bd5\u6b65\u9aa4\u63cf\u8ff0\uff08testing procedure\uff09 \u660e\u786e\u7684\u9884\u671f\u7ed3\u679c\uff08expected results\uff09 \u5bf9\u5e94\u5730\uff0cHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u63cf\u8ff0\u65b9\u5f0f\u8fdb\u884c\u5982\u4e0b\u8bbe\u8ba1\uff1a \u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u5b8c\u6574\u4e14\u72ec\u7acb\u7684\uff0c\u6bcf\u6761\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u90fd\u53ef\u4ee5\u72ec\u7acb\u8fd0\u884c\u7684\uff1b\u5728 HttpRunner \u4e2d\uff0c\u6bcf\u4e2a YAML/JSON \u6587\u4ef6\u5bf9\u5e94\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\u3002 \u6d4b\u8bd5\u7528\u4f8b\u5305\u542b \u6d4b\u8bd5\u811a\u672c \u548c \u6d4b\u8bd5\u6570\u636e \u4e24\u90e8\u5206\uff1a \u6d4b\u8bd5\u7528\u4f8b = \u6d4b\u8bd5\u811a\u672c + \u6d4b\u8bd5\u6570\u636e \u6d4b\u8bd5\u811a\u672c \u91cd\u70b9\u662f\u63cf\u8ff0\u6d4b\u8bd5\u7684 \u4e1a\u52a1\u529f\u80fd\u903b\u8f91 \uff0c\u5305\u62ec\u9884\u7f6e\u6761\u4ef6\u3001\u6d4b\u8bd5\u6b65\u9aa4\u3001\u9884\u671f\u7ed3\u679c\u7b49\uff0c\u5e76\u4e14\u53ef\u4ee5\u7ed3\u5408\u8f85\u52a9\u51fd\u6570\uff08debugtalk.py\uff09\u5b9e\u73b0\u590d\u6742\u7684\u8fd0\u7b97\u903b\u8f91\uff1b\u53ef\u4ee5\u5c06 \u6d4b\u8bd5\u811a\u672c \u7406\u89e3\u4e3a\u7f16\u7a0b\u8bed\u8a00\u4e2d\u7684 \u7c7b\uff08class\uff09 \uff1b \u6d4b\u8bd5\u6570\u636e \u91cd\u70b9\u662f\u5bf9\u5e94\u6d4b\u8bd5\u7684 \u4e1a\u52a1\u6570\u636e\u903b\u8f91 \uff0c\u53ef\u4ee5\u7406\u89e3\u4e3a\u7c7b\u7684\u5b9e\u4f8b\u5316\u6570\u636e\uff1b \u6d4b\u8bd5\u6570\u636e \u548c \u6d4b\u8bd5\u811a\u672c \u5206\u79bb\u540e\uff0c\u5c31\u53ef\u4ee5\u6bd4\u8f83\u65b9\u4fbf\u5730\u5b9e\u73b0\u6570\u636e\u9a71\u52a8\u6d4b\u8bd5\uff0c\u901a\u8fc7\u5bf9\u6d4b\u8bd5\u811a\u672c\u4f20\u5165\u4e00\u7ec4\u6570\u636e\uff0c\u5b9e\u73b0\u540c\u4e00\u4e1a\u52a1\u529f\u80fd\u5728\u4e0d\u540c\u6570\u636e\u903b\u8f91\u4e0b\u7684\u6d4b\u8bd5\u9a8c\u8bc1\u3002 \u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09 \u00b6 \u6d4b\u8bd5\u7528\u4f8b\u662f\u6d4b\u8bd5\u6b65\u9aa4\u7684 \u6709\u5e8f \u96c6\u5408\uff0c\u800c\u5bf9\u4e8e\u63a5\u53e3\u6d4b\u8bd5\u6765\u8bf4\uff0c\u6bcf\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5e94\u8be5\u5c31\u5bf9\u5e94\u4e00\u4e2a API \u7684\u8bf7\u6c42\u63cf\u8ff0\u3002 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09 \u00b6 \u6d4b\u8bd5\u7528\u4f8b\u96c6 \u662f \u6d4b\u8bd5\u7528\u4f8b \u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\u3002 \u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u600e\u4e48\u529e\uff0c\u4f8b\u5982\u767b\u5f55\u529f\u80fd\u548c\u4e0b\u5355\u529f\u80fd\u3002\u6b63\u786e\u7684\u505a\u6cd5\u5e94\u8be5\u662f\uff0c\u5728\u4e0b\u5355\u6d4b\u8bd5\u7528\u4f8b\u7684\u524d\u7f6e\u6b65\u9aa4\u4e2d\u6267\u884c\u767b\u5f55\u64cd\u4f5c\u3002 - config : name : order product - test : name : login testcase : testcases/login.yml - test : name : add to cart api : api/add_cart.yml - test : name : make order api : api/make_order.yml \u6d4b\u8bd5\u573a\u666f \u00b6 \u6d4b\u8bd5\u573a\u666f \u548c \u6d4b\u8bd5\u7528\u4f8b\u96c6 \u662f\u540c\u4e00\u6982\u5ff5\uff0c\u90fd\u662f \u6d4b\u8bd5\u7528\u4f8b \u7684 \u65e0\u5e8f \u96c6\u5408\u3002 \u63a5\u53e3 \u6d4b\u8bd5\u7528\u4f8b\u96c6 \u53c2\u6570 \u53d8\u91cf \u6d4b\u8bd5\u811a\u672c\uff08YAML/JSON\uff09 debugtalk.py \u73af\u5883\u53d8\u91cf \u9879\u76ee\u6839\u76ee\u5f55 \u00b6","title":"\u540d\u8bcd\u89e3\u91ca"},{"location":"concept/nominal/#testcase","text":"\u4ece 2.0 \u7248\u672c\u5f00\u59cb\uff0cHttpRunner \u5f00\u59cb\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u7684\u5b9a\u4e49\u8fdb\u884c\u8fdb\u4e00\u6b65\u7684\u660e\u786e\uff0c\u53c2\u8003 wiki \u4e0a\u7684\u63cf\u8ff0\u3002 A test case is a specification of the inputs, execution conditions, testing procedure, and expected results that define a single test to be executed to achieve a particular software testing objective, such as to exercise a particular program path or to verify compliance with a specific requirement. \u6982\u62ec\u4e0b\u6765\uff0c\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5e94\u8be5\u662f\u4e3a\u4e86\u6d4b\u8bd5\u67d0\u4e2a\u7279\u5b9a\u7684\u529f\u80fd\u903b\u8f91\u800c\u7cbe\u5fc3\u8bbe\u8ba1\u7684\uff0c\u5e76\u4e14\u81f3\u5c11\u5305\u542b\u5982\u4e0b\u51e0\u70b9\uff1a \u660e\u786e\u7684\u6d4b\u8bd5\u76ee\u7684\uff08achieve a particular software testing objective\uff09 \u660e\u786e\u7684\u8f93\u5165\uff08inputs\uff09 \u660e\u786e\u7684\u8fd0\u884c\u73af\u5883\uff08execution conditions\uff09 \u660e\u786e\u7684\u6d4b\u8bd5\u6b65\u9aa4\u63cf\u8ff0\uff08testing procedure\uff09 \u660e\u786e\u7684\u9884\u671f\u7ed3\u679c\uff08expected results\uff09 \u5bf9\u5e94\u5730\uff0cHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u63cf\u8ff0\u65b9\u5f0f\u8fdb\u884c\u5982\u4e0b\u8bbe\u8ba1\uff1a \u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u5b8c\u6574\u4e14\u72ec\u7acb\u7684\uff0c\u6bcf\u6761\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u90fd\u53ef\u4ee5\u72ec\u7acb\u8fd0\u884c\u7684\uff1b\u5728 HttpRunner \u4e2d\uff0c\u6bcf\u4e2a YAML/JSON \u6587\u4ef6\u5bf9\u5e94\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\u3002 \u6d4b\u8bd5\u7528\u4f8b\u5305\u542b \u6d4b\u8bd5\u811a\u672c \u548c \u6d4b\u8bd5\u6570\u636e \u4e24\u90e8\u5206\uff1a \u6d4b\u8bd5\u7528\u4f8b = \u6d4b\u8bd5\u811a\u672c + \u6d4b\u8bd5\u6570\u636e \u6d4b\u8bd5\u811a\u672c \u91cd\u70b9\u662f\u63cf\u8ff0\u6d4b\u8bd5\u7684 \u4e1a\u52a1\u529f\u80fd\u903b\u8f91 \uff0c\u5305\u62ec\u9884\u7f6e\u6761\u4ef6\u3001\u6d4b\u8bd5\u6b65\u9aa4\u3001\u9884\u671f\u7ed3\u679c\u7b49\uff0c\u5e76\u4e14\u53ef\u4ee5\u7ed3\u5408\u8f85\u52a9\u51fd\u6570\uff08debugtalk.py\uff09\u5b9e\u73b0\u590d\u6742\u7684\u8fd0\u7b97\u903b\u8f91\uff1b\u53ef\u4ee5\u5c06 \u6d4b\u8bd5\u811a\u672c \u7406\u89e3\u4e3a\u7f16\u7a0b\u8bed\u8a00\u4e2d\u7684 \u7c7b\uff08class\uff09 \uff1b \u6d4b\u8bd5\u6570\u636e \u91cd\u70b9\u662f\u5bf9\u5e94\u6d4b\u8bd5\u7684 \u4e1a\u52a1\u6570\u636e\u903b\u8f91 \uff0c\u53ef\u4ee5\u7406\u89e3\u4e3a\u7c7b\u7684\u5b9e\u4f8b\u5316\u6570\u636e\uff1b \u6d4b\u8bd5\u6570\u636e \u548c \u6d4b\u8bd5\u811a\u672c \u5206\u79bb\u540e\uff0c\u5c31\u53ef\u4ee5\u6bd4\u8f83\u65b9\u4fbf\u5730\u5b9e\u73b0\u6570\u636e\u9a71\u52a8\u6d4b\u8bd5\uff0c\u901a\u8fc7\u5bf9\u6d4b\u8bd5\u811a\u672c\u4f20\u5165\u4e00\u7ec4\u6570\u636e\uff0c\u5b9e\u73b0\u540c\u4e00\u4e1a\u52a1\u529f\u80fd\u5728\u4e0d\u540c\u6570\u636e\u903b\u8f91\u4e0b\u7684\u6d4b\u8bd5\u9a8c\u8bc1\u3002","title":"\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09"},{"location":"concept/nominal/#teststep","text":"\u6d4b\u8bd5\u7528\u4f8b\u662f\u6d4b\u8bd5\u6b65\u9aa4\u7684 \u6709\u5e8f \u96c6\u5408\uff0c\u800c\u5bf9\u4e8e\u63a5\u53e3\u6d4b\u8bd5\u6765\u8bf4\uff0c\u6bcf\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5e94\u8be5\u5c31\u5bf9\u5e94\u4e00\u4e2a API \u7684\u8bf7\u6c42\u63cf\u8ff0\u3002","title":"\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09"},{"location":"concept/nominal/#testsuite","text":"\u6d4b\u8bd5\u7528\u4f8b\u96c6 \u662f \u6d4b\u8bd5\u7528\u4f8b \u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\u3002 \u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u600e\u4e48\u529e\uff0c\u4f8b\u5982\u767b\u5f55\u529f\u80fd\u548c\u4e0b\u5355\u529f\u80fd\u3002\u6b63\u786e\u7684\u505a\u6cd5\u5e94\u8be5\u662f\uff0c\u5728\u4e0b\u5355\u6d4b\u8bd5\u7528\u4f8b\u7684\u524d\u7f6e\u6b65\u9aa4\u4e2d\u6267\u884c\u767b\u5f55\u64cd\u4f5c\u3002 - config : name : order product - test : name : login testcase : testcases/login.yml - test : name : add to cart api : api/add_cart.yml - test : name : make order api : api/make_order.yml","title":"\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09"},{"location":"concept/nominal/#_1","text":"\u6d4b\u8bd5\u573a\u666f \u548c \u6d4b\u8bd5\u7528\u4f8b\u96c6 \u662f\u540c\u4e00\u6982\u5ff5\uff0c\u90fd\u662f \u6d4b\u8bd5\u7528\u4f8b \u7684 \u65e0\u5e8f \u96c6\u5408\u3002 \u63a5\u53e3 \u6d4b\u8bd5\u7528\u4f8b\u96c6 \u53c2\u6570 \u53d8\u91cf \u6d4b\u8bd5\u811a\u672c\uff08YAML/JSON\uff09 debugtalk.py \u73af\u5883\u53d8\u91cf","title":"\u6d4b\u8bd5\u573a\u666f"},{"location":"concept/nominal/#_2","text":"","title":"\u9879\u76ee\u6839\u76ee\u5f55"},{"location":"development/architecture/","text":"","title":"Pipline"},{"location":"development/dev-api/","text":"\u5f00\u53d1\u6269\u5c55 \u00b6 HttpRunner \u9664\u4e86\u4f5c\u4e3a\u547d\u4ee4\u884c\u5de5\u5177\u4f7f\u7528\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f5c\u4e3a\u8f6f\u4ef6\u5305\u96c6\u6210\u5230\u4f60\u81ea\u5df1\u7684\u9879\u76ee\u4e2d\u3002 \u7b80\u5355\u6765\u8bf4\uff0cHttpRunner \u63d0\u4f9b\u4e86\u8fd0\u884c YAML/JSON \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u7684\u80fd\u529b\uff0c\u5e76\u80fd\u8fd4\u56de\u8be6\u7ec6\u7684\u6d4b\u8bd5\u7ed3\u679c\u4fe1\u606f\u3002 HttpRunner class \u00b6 HttpRunner \u4ee5 \u7c7b\uff08class\uff09 \u7684\u5f62\u5f0f\u5bf9\u5916\u63d0\u4f9b\u8c03\u7528\u652f\u6301\uff0c\u7c7b\u540d\b\u4e3a HttpRunner \uff0c\u5bfc\u5165\u65b9\u5f0f\u5982\u4e0b\uff1a from httprunner.api import HttpRunner \u53ef\u7528\u521d\u59cb\u5316\u53c2\u6570 \u00b6 HttpRunner \u5185\u90e8\b\u7528\u4e8e\u9a71\u52a8\u6d4b\u8bd5\u6267\u884c\u7684\u662f unittest.TextTestRunner \uff0c\u5728\u521d\u59cb\u5316 HttpRunner \u65f6\u53ef\u4ee5\u4f7f\u7528 TextTestRunner \u7684\u6240\u6709\u53ef\u7528\u53c2\u6570\uff08\u8be6\u60c5\u53ef\u9605\u8bfb \u5b98\u65b9\u6587\u6863 \uff09\u3002\u9664\u6b64\u4e4b\u5916\uff0c HttpRunner \u8fd8\u989d\u5916\u652f\u6301\u4e00\u4e2a\u53c2\u6570\uff0c http_client_session \uff0c\u53ef\u7528\u4e8e\u6307\u5b9a\u4e0d\u540c\u7684\u5ba2\u6237\u7aef\u7c7b\u578b\u3002 \u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u521d\u59cb\u5316 HttpRunner \u65f6\u5e38\u7528\u7684\u53c2\u6570\u6709\u5982\u4e0b\u51e0\u4e2a\uff1a resultclass : HtmlTestResult/TextTestResult\uff0c\u9ed8\u8ba4\u503c\u4e3a HtmlTestResult failfast : \u8bbe\u7f6e\u4e3a True \u65f6\uff0c\u6d4b\u8bd5\u5728\u9996\u6b21\u9047\u5230\u9519\u8bef\u6216\u5931\u8d25\u65f6\u4f1a\u505c\u6b62\u8fd0\u884c\uff1b\u9ed8\u8ba4\u503c\u4e3a False http_client_session : \u4f20\u5165 requests.Session() \u65f6\u8fdb\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\uff08\u9ed8\u8ba4\uff09\uff0c\u4f20\u5165 locust.client.Session() \u65f6\u8fdb\u884c\u6027\u80fd\u6d4b\u8bd5 \u4f8b\u5982\uff0c\u5982\u9700\u521d\u59cb\u5316 HttpRunner \u65f6\u8bbe\u7f6e failfast \u4e3a False\uff0c\b\u521d\u59cb\u5316\u65b9\u5f0f\u5982\u4e0b\u6240\u793a\uff1a from httprunner.api import HttpRunner runner = HttpRunner ( failfast = False ) \b\u53ef\u7528\u8c03\u7528\u65b9\u6cd5 \u00b6 \u5728 HttpRunner \u4e2d\uff0c\u5bf9\u5916\u63d0\u4f9b\u4e86\u4e24\u4e2a\u65b9\u6cd5\uff1a run : \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b gen_html_report : \u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a \u53ef\u7528\u5c5e\u6027 \u00b6 \u5728 HttpRunner \u4e2d\uff0c\u5bf9\u5916\u63d0\u4f9b\u4e86\u4e00\u4e2a\u5c5e\u6027\uff1a summary : \u6d4b\u8bd5\u6267\u884c\u7ed3\u679c \u8be5\u5c5e\u6027\u9700\u8981\u5728\u8c03\u7528 run \u65b9\u6cd5\u540e\u83b7\u53d6\u3002 \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b \u00b6 HttpRunner \u7684 run \u65b9\u6cd5\u6709\u4e09\u4e2a\u53c2\u6570\uff1a path_or_testcases : \u6307\u5b9a\u8981\u8fd0\u884c\u7684\u6d4b\u8bd5\u7528\u4f8b\uff1b\u652f\u6301\u4f20\u5165\u4e24\u7c7b\u53c2\u6570\uff0cYAML/JSON \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\uff0c\u6216\u8005\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\uff1b dot_env_path \uff08\u53ef\u9009\uff09: \u6307\u5b9a\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\u6587\u4ef6\uff08.env\uff09\u7684\u8def\u5f84\uff0c\u9ed8\u8ba4\u503c\u4e3a\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u4e2d\u7684 .env \u6587\u4ef6 mapping \uff08\u53ef\u9009\uff09: \u53d8\u91cf\u6620\u5c04\uff0c\u53ef\u7528\u4e8e\u5bf9\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u7684\u53d8\u91cf\u8fdb\u884c\u8986\u76d6\u66ff\u6362\u3002 \u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84 \u00b6 \u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u652f\u6301\u4e09\u79cd\u5f62\u5f0f\uff1a YAML/JSON \u6587\u4ef6\u8def\u5f84\uff0c\u652f\u6301\u7edd\u5bf9\u8def\u5f84\u548c\u76f8\u5bf9\u8def\u5f84 \u5305\u542b YAML/JSON \u6587\u4ef6\u7684\u6587\u4ef6\u5939\uff0c\u652f\u6301\u7edd\u5bf9\u8def\u5f84\u548c\u76f8\u5bf9\u8def\u5f84 \u6587\u4ef6\u8def\u5f84\u548c\u6587\u4ef6\u5939\u8def\u5f84\u7684\u6df7\u5408\u60c5\u51b5\uff08list/set\uff09 # \u6587\u4ef6\u8def\u5f84 runner . run ( \"docs/data/demo-quickstart-2.yml\" ) # \u6587\u4ef6\u5939\u8def\u5f84 runner . run ( \"docs/data/\" ) # \u6df7\u5408\u60c5\u51b5 runner . run ([ \"docs/data/\" , \"files/demo-quickstart-2.yml\" ]) \u5982\u9700\u6307\u5b9a\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\u6587\u4ef6\uff08.env\uff09\u7684\u8def\u5f84\uff0c\u6216\u8005\u9700\u8981\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u7684\u53d8\u91cf\u8fdb\u884c\u8986\u76d6\u66ff\u6362\uff0c\u5219\u53ef\u4f7f\u7528 dot_env_path \u548c mapping \u53c2\u6570\u3002 # dot_env_path runner . run ( \"docs/data/demo-quickstart-2.yml\" , dot_env_path = \"/path/to/.env\" ) # mapping override_mapping = { \"device_sn\" : \"XXX\" } runner . run ( \"docs/data/demo-quickstart-2.yml\" , mapping = override_mapping ) \u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53 \u00b6 \u9664\u4e86\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\uff0c\u8fd8\u53ef\u4ee5\u76f4\u63a5\u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u3002 \u4ee5 demo-quickstart-2.yml \u4e3a\u4f8b\uff0c\u5bf9\u5e94\u7684\u6570\u636e\u7ed3\u6784\u4f53\u5982\u4e0b\u6240\u793a\uff1a [ { \"config\" : { \"name\" : \"testcase description\" , \"request\" : { \"base_url\" : \"\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" } }, \"variables\" : [], \"output\" : [ \"token\" ], \"path\" : \"/abs-path/to/demo-quickstart-2.yml\" , \"refs\" : { \"env\" : {}, \"debugtalk\" : { \"variables\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"functions\" : { \"gen_random_string\" : , \"get_sign\" : , \"get_user_id\" : , \"get_account\" : , \"get_os_platform\" : } }, \"def-api\" : {}, \"def-testcase\" : {} } }, \"teststeps\" : [ { \"name\" : \"/api/get-token\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"app_version\" : \"2.8.6\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"os_platform\" : \"ios\" , \"user_agent\" : \"iOS/10.3\" }, \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" } }, \"extract\" : [ { \"token\" : \"content.token\" } ], \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]} ] }, { \"name\" : \"/api/users/1000\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"method\" : \"POST\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"$token\" }, \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }}, \"validate\" : [ { \"eq\" : [ \"status_code\" , 201 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]}, { \"eq\" : [ \"content.msg\" , \"user created successfully.\" ]} ] } ] }, { ... } # another testcase ] \u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u65f6\uff0c\u652f\u6301\u4f20\u5165\u5355\u4e2a\u7ed3\u6784\u4f53\uff08dict\uff09\uff0c\u6216\u8005\u591a\u4e2a\u7ed3\u6784\u4f53\uff08list of dict\uff09\u3002 # \u8fd0\u884c\u5355\u4e2a\u7ed3\u6784\u4f53 runner . run ( testcase ) # \u8fd0\u884c\u591a\u4e2a\u7ed3\u6784\u4f53 runner . run ([ testcase1 , testcase2 ]) \u52a0\u8f7d debugtalk.py && .env \u00b6 \b\u901a\u8fc7\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\bHttpRunner \u4f1a\u81ea\u52a8\u4ee5\b\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u4e3a\u8d77\u70b9\uff0c\u5411\u4e0a\u641c\u7d22 debugtalk.py \u6587\u4ef6\uff0c\u5e76\u5c06 debugtalk.py \u6587\u4ef6\u6240\u5728\u7684\u6587\u4ef6\u76ee\u5f55\u4f5c\u4e3a\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u3002 \u540c\u65f6\uff0cHttpRunner \u4f1a\u5728\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u4e0b\b\b\u641c\u7d22 .env \u6587\u4ef6\uff0c\u4ee5\u53ca api \u548c testcases \u6587\u4ef6\u5939\uff0c\u5e76\u81ea\u52a8\u8fdb\u884c\u52a0\u8f7d\u3002 \u6700\u7ec8\u52a0\u8f7d\u5f97\u5230\u7684\u5b58\u50a8\u7ed3\u6784\u5982\u4e0b\u6240\u793a\uff1a { \"env\" : {}, \"debugtalk\" : { \"variables\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"functions\" : { \"gen_random_string\" : , \"get_sign\" : , \"get_user_id\" : , \"get_account\" : , \"get_os_platform\" : } }, \"def-api\" : {}, \"def-testcase\" : {} } \u5176\u4e2d\uff0c env \u5bf9\u5e94\u7684\u662f .env \u6587\u4ef6\u4e2d\u7684\u73af\u5883\u53d8\u91cf\uff0c debugtalk \u5bf9\u5e94\u7684\u662f debugtalk.py \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u548c\u51fd\u6570\uff0c def-api \u5bf9\u5e94\u7684\u662f api \u6587\u4ef6\u5939\u4e0b\u5b9a\u4e49\u7684\u63a5\u53e3\u63cf\u8ff0\uff0c def-testcase \u5bf9\u5e94\u7684\u662f testcases \u6587\u4ef6\u5939\u4e0b\u5b9a\u4e49\u7684\u6d4b\u8bd5\u7528\u4f8b\u3002 \u901a\u8fc7\u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u6267\u884c\u6d4b\u8bd5\u65f6\uff0c\u4f20\u5165\u7684\u6570\u636e\u5e94\u5305\u542b\u6240\u6709\u4fe1\u606f\uff0c\u5305\u62ec debugtalk.py \u3001 .env \u3001\u4f9d\u8d56\u7684 api \u548c \u6d4b\u8bd5\u7528\u4f8b\u7b49\uff1b\u56e0\u6b64\u4e5f\u65e0\u9700\u518d\u4f7f\u7528 dot_env_path \u548c mapping \u53c2\u6570\uff0c\u6240\u6709\u4fe1\u606f\u90fd\u8981\u901a\u8fc7 refs \u4f20\u5165\u3002 \u8fd4\u56de\u8be6\u7ec6\u6d4b\u8bd5\u7ed3\u679c\u6570\u636e \u00b6 \u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u901a\u8fc7 summary \u5c5e\u6027\u53ef\u83b7\u53d6\u8be6\u5c3d\u7684\u8fd0\u884c\u7ed3\u679c\u6570\u636e\u3002 # get result summary summary = runner . summary \u5176\u6570\u636e\u7ed3\u6784\u4e3a\uff1a { \"success\" : true , \"stat\" : { \"testsRun\" : 2 , \"failures\" : 0 , \"errors\" : 0 , \"skipped\" : 0 , \"expectedFailures\" : 0 , \"unexpectedSuccesses\" : 0 , \"successes\" : 2 }, \"time\" : { \"start_at\" : 1538449655.944404 , \"duration\" : 0.03181314468383789 }, \"platform\" : { \"httprunner_version\" : \"1.5.14\" , \"python_version\" : \"CPython 3.6.5+\" , \"platform\" : \"Darwin-17.6.0-x86_64-i386-64bit\" }, \"details\" : [ { \"success\" : true , \"name\" : \"testcase description\" , \"base_url\" : \"\" , \"stat\" : { \"testsRun\" : 2 , \"failures\" : 0 , \"errors\" : 0 , \"skipped\" : 0 , \"expectedFailures\" : 0 , \"unexpectedSuccesses\" : 0 , \"successes\" : 2 }, \"time\" : { \"start_at\" : 1538449655.944404 , \"duration\" : 0.03181314468383789 }, \"records\" : [ { \"name\" : \"/api/get-token\" , \"status\" : \"success\" , \"attachment\" : \"\" , \"meta_data\" : { \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" , \"Accept-Encoding\" : \"gzip, deflate\" , \"Accept\" : \"*/*\" , \"Connection\" : \"keep-alive\" , \"Content-Type\" : \"application/json\" , \"app_version\" : \"2.8.6\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"os_platform\" : \"ios\" , \"user_agent\" : \"iOS/10.3\" , \"Content-Length\" : \"52\" }, \"start_timestamp\" : 1538449655.944801 , \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" }, \"body\" : b' { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" } ' }, \"response\" : { \"status_code\" : 200 , \"headers\" : { \"Content-Type\" : \"application/json\" , \"Content-Length\" : \"46\" , \"Server\" : \"Werkzeug/0.14.1 Python/3.6.5+\" , \"Date\" : \"Tue, 02 Oct 2018 03:07:35 GMT\" }, \"content_size\" : 46 , \"response_time_ms\" : 12.87 , \"elapsed_ms\" : 6.955 , \"encoding\" : null , \"content\" : b' { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } ' , \"content_type\" : \"application/json\" , \"ok\" : true , \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"reason\" : \"OK\" , \"cookies\" : {}, \"text\" : ' { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } ' , \"json\" : { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } }, \"validators\" : [ { \"check\" : \"status_code\" , \"expect\" : 200 , \"comparator\" : \"eq\" , \"check_value\" : 200 , \"check_result\" : \"pass\" }, { \"check\" : \"headers.Content-Type\" , \"expect\" : \"application/json\" , \"comparator\" : \"eq\" , \"check_value\" : \"application/json\" , \"check_result\" : \"pass\" }, { \"check\" : \"content.success\" , \"expect\" : true , \"comparator\" : \"eq\" , \"check_value\" : true , \"check_result\" : \"pass\" } ] } }, { \"name\" : \"/api/users/1000\" , \"status\" : \"success\" , \"attachment\" : \"\" , \"meta_data\" : { \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"method\" : \"POST\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" , \"Accept-Encoding\" : \"gzip, deflate\" , \"Accept\" : \"*/*\" , \"Connection\" : \"keep-alive\" , \"Content-Type\" : \"application/json\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"CcQ7dBjZZbjIXRkG\" , \"Content-Length\" : \"39\" }, \"start_timestamp\" : 1538449655.958944 , \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }, \"body\" : b' { \"name\" : \"user1\" , \"password\" : \"123456\" } ' }, \"response\" : { \"status_code\" : 201 , \"headers\" : { \"Content-Type\" : \"application/json\" , \"Content-Length\" : \"54\" , \"Server\" : \"Werkzeug/0.14.1 Python/3.6.5+\" , \"Date\" : \"Tue, 02 Oct 2018 03:07:35 GMT\" }, \"content_size\" : 54 , \"response_time_ms\" : 3.34 , \"elapsed_ms\" : 2.16 , \"encoding\" : null , \"content\" : b' { \"success\" : true , \"msg\" : \"user created successfully.\" } ' , \"content_type\" : \"application/json\" , \"ok\" : true , \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"reason\" : \"CREATED\" , \"cookies\" : {}, \"text\" : ' { \"success\" : true , \"msg\" : \"user created successfully.\" } ' , \"json\" : { \"success\" : true , \"msg\" : \"user created successfully.\" } }, \"validators\" : [ { \"check\" : \"status_code\" , \"expect\" : 201 , \"comparator\" : \"eq\" , \"check_value\" : 201 , \"check_result\" : \"pass\" }, { \"check\" : \"headers.Content-Type\" , \"expect\" : \"application/json\" , \"comparator\" : \"eq\" , \"check_value\" : \"application/json\" , \"check_result\" : \"pass\" }, { \"check\" : \"content.success\" , \"expect\" : true , \"comparator\" : \"eq\" , \"check_value\" : true , \"check_result\" : \"pass\" }, { \"check\" : \"content.msg\" , \"expect\" : \"user created successfully.\" , \"comparator\" : \"eq\" , \"check_value\" : \"user created successfully.\" , \"check_result\" : \"pass\" } ] } } ], \"in_out\" : { \"in\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"out\" : { \"token\" : \"CcQ7dBjZZbjIXRkG\" } } } ] } \u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a \u00b6 \u5982\u9700\u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a\uff0c\u53ef\u8c03\u7528 gen_html_report \u65b9\u6cd5\u3002 # generate html report runner . gen_html_report ( html_report_name = \"demo\" , html_report_template = \"/path/to/custom_report_template\" ) # => reports/demo/demo-1532078874.html \u5173\u4e8e\u6d4b\u8bd5\u62a5\u544a\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u90e8\u5206\u3002","title":"\u57fa\u7840\u5e93\u8c03\u7528"},{"location":"development/dev-api/#_1","text":"HttpRunner \u9664\u4e86\u4f5c\u4e3a\u547d\u4ee4\u884c\u5de5\u5177\u4f7f\u7528\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f5c\u4e3a\u8f6f\u4ef6\u5305\u96c6\u6210\u5230\u4f60\u81ea\u5df1\u7684\u9879\u76ee\u4e2d\u3002 \u7b80\u5355\u6765\u8bf4\uff0cHttpRunner \u63d0\u4f9b\u4e86\u8fd0\u884c YAML/JSON \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u7684\u80fd\u529b\uff0c\u5e76\u80fd\u8fd4\u56de\u8be6\u7ec6\u7684\u6d4b\u8bd5\u7ed3\u679c\u4fe1\u606f\u3002","title":"\u5f00\u53d1\u6269\u5c55"},{"location":"development/dev-api/#httprunner-class","text":"HttpRunner \u4ee5 \u7c7b\uff08class\uff09 \u7684\u5f62\u5f0f\u5bf9\u5916\u63d0\u4f9b\u8c03\u7528\u652f\u6301\uff0c\u7c7b\u540d\b\u4e3a HttpRunner \uff0c\u5bfc\u5165\u65b9\u5f0f\u5982\u4e0b\uff1a from httprunner.api import HttpRunner","title":"HttpRunner class"},{"location":"development/dev-api/#_2","text":"HttpRunner \u5185\u90e8\b\u7528\u4e8e\u9a71\u52a8\u6d4b\u8bd5\u6267\u884c\u7684\u662f unittest.TextTestRunner \uff0c\u5728\u521d\u59cb\u5316 HttpRunner \u65f6\u53ef\u4ee5\u4f7f\u7528 TextTestRunner \u7684\u6240\u6709\u53ef\u7528\u53c2\u6570\uff08\u8be6\u60c5\u53ef\u9605\u8bfb \u5b98\u65b9\u6587\u6863 \uff09\u3002\u9664\u6b64\u4e4b\u5916\uff0c HttpRunner \u8fd8\u989d\u5916\u652f\u6301\u4e00\u4e2a\u53c2\u6570\uff0c http_client_session \uff0c\u53ef\u7528\u4e8e\u6307\u5b9a\u4e0d\u540c\u7684\u5ba2\u6237\u7aef\u7c7b\u578b\u3002 \u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u521d\u59cb\u5316 HttpRunner \u65f6\u5e38\u7528\u7684\u53c2\u6570\u6709\u5982\u4e0b\u51e0\u4e2a\uff1a resultclass : HtmlTestResult/TextTestResult\uff0c\u9ed8\u8ba4\u503c\u4e3a HtmlTestResult failfast : \u8bbe\u7f6e\u4e3a True \u65f6\uff0c\u6d4b\u8bd5\u5728\u9996\u6b21\u9047\u5230\u9519\u8bef\u6216\u5931\u8d25\u65f6\u4f1a\u505c\u6b62\u8fd0\u884c\uff1b\u9ed8\u8ba4\u503c\u4e3a False http_client_session : \u4f20\u5165 requests.Session() \u65f6\u8fdb\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\uff08\u9ed8\u8ba4\uff09\uff0c\u4f20\u5165 locust.client.Session() \u65f6\u8fdb\u884c\u6027\u80fd\u6d4b\u8bd5 \u4f8b\u5982\uff0c\u5982\u9700\u521d\u59cb\u5316 HttpRunner \u65f6\u8bbe\u7f6e failfast \u4e3a False\uff0c\b\u521d\u59cb\u5316\u65b9\u5f0f\u5982\u4e0b\u6240\u793a\uff1a from httprunner.api import HttpRunner runner = HttpRunner ( failfast = False )","title":"\u53ef\u7528\u521d\u59cb\u5316\u53c2\u6570"},{"location":"development/dev-api/#_3","text":"\u5728 HttpRunner \u4e2d\uff0c\u5bf9\u5916\u63d0\u4f9b\u4e86\u4e24\u4e2a\u65b9\u6cd5\uff1a run : \u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b gen_html_report : \u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a","title":"\b\u53ef\u7528\u8c03\u7528\u65b9\u6cd5"},{"location":"development/dev-api/#_4","text":"\u5728 HttpRunner \u4e2d\uff0c\u5bf9\u5916\u63d0\u4f9b\u4e86\u4e00\u4e2a\u5c5e\u6027\uff1a summary : \u6d4b\u8bd5\u6267\u884c\u7ed3\u679c \u8be5\u5c5e\u6027\u9700\u8981\u5728\u8c03\u7528 run \u65b9\u6cd5\u540e\u83b7\u53d6\u3002","title":"\u53ef\u7528\u5c5e\u6027"},{"location":"development/dev-api/#_5","text":"HttpRunner \u7684 run \u65b9\u6cd5\u6709\u4e09\u4e2a\u53c2\u6570\uff1a path_or_testcases : \u6307\u5b9a\u8981\u8fd0\u884c\u7684\u6d4b\u8bd5\u7528\u4f8b\uff1b\u652f\u6301\u4f20\u5165\u4e24\u7c7b\u53c2\u6570\uff0cYAML/JSON \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\uff0c\u6216\u8005\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\uff1b dot_env_path \uff08\u53ef\u9009\uff09: \u6307\u5b9a\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\u6587\u4ef6\uff08.env\uff09\u7684\u8def\u5f84\uff0c\u9ed8\u8ba4\u503c\u4e3a\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u4e2d\u7684 .env \u6587\u4ef6 mapping \uff08\u53ef\u9009\uff09: \u53d8\u91cf\u6620\u5c04\uff0c\u53ef\u7528\u4e8e\u5bf9\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u7684\u53d8\u91cf\u8fdb\u884c\u8986\u76d6\u66ff\u6362\u3002","title":"\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b"},{"location":"development/dev-api/#_6","text":"\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u652f\u6301\u4e09\u79cd\u5f62\u5f0f\uff1a YAML/JSON \u6587\u4ef6\u8def\u5f84\uff0c\u652f\u6301\u7edd\u5bf9\u8def\u5f84\u548c\u76f8\u5bf9\u8def\u5f84 \u5305\u542b YAML/JSON \u6587\u4ef6\u7684\u6587\u4ef6\u5939\uff0c\u652f\u6301\u7edd\u5bf9\u8def\u5f84\u548c\u76f8\u5bf9\u8def\u5f84 \u6587\u4ef6\u8def\u5f84\u548c\u6587\u4ef6\u5939\u8def\u5f84\u7684\u6df7\u5408\u60c5\u51b5\uff08list/set\uff09 # \u6587\u4ef6\u8def\u5f84 runner . run ( \"docs/data/demo-quickstart-2.yml\" ) # \u6587\u4ef6\u5939\u8def\u5f84 runner . run ( \"docs/data/\" ) # \u6df7\u5408\u60c5\u51b5 runner . run ([ \"docs/data/\" , \"files/demo-quickstart-2.yml\" ]) \u5982\u9700\u6307\u5b9a\u52a0\u8f7d\u73af\u5883\u53d8\u91cf\u6587\u4ef6\uff08.env\uff09\u7684\u8def\u5f84\uff0c\u6216\u8005\u9700\u8981\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u7684\u53d8\u91cf\u8fdb\u884c\u8986\u76d6\u66ff\u6362\uff0c\u5219\u53ef\u4f7f\u7528 dot_env_path \u548c mapping \u53c2\u6570\u3002 # dot_env_path runner . run ( \"docs/data/demo-quickstart-2.yml\" , dot_env_path = \"/path/to/.env\" ) # mapping override_mapping = { \"device_sn\" : \"XXX\" } runner . run ( \"docs/data/demo-quickstart-2.yml\" , mapping = override_mapping )","title":"\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84"},{"location":"development/dev-api/#_7","text":"\u9664\u4e86\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\uff0c\u8fd8\u53ef\u4ee5\u76f4\u63a5\u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u3002 \u4ee5 demo-quickstart-2.yml \u4e3a\u4f8b\uff0c\u5bf9\u5e94\u7684\u6570\u636e\u7ed3\u6784\u4f53\u5982\u4e0b\u6240\u793a\uff1a [ { \"config\" : { \"name\" : \"testcase description\" , \"request\" : { \"base_url\" : \"\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" } }, \"variables\" : [], \"output\" : [ \"token\" ], \"path\" : \"/abs-path/to/demo-quickstart-2.yml\" , \"refs\" : { \"env\" : {}, \"debugtalk\" : { \"variables\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"functions\" : { \"gen_random_string\" : , \"get_sign\" : , \"get_user_id\" : , \"get_account\" : , \"get_os_platform\" : } }, \"def-api\" : {}, \"def-testcase\" : {} } }, \"teststeps\" : [ { \"name\" : \"/api/get-token\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"app_version\" : \"2.8.6\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"os_platform\" : \"ios\" , \"user_agent\" : \"iOS/10.3\" }, \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" } }, \"extract\" : [ { \"token\" : \"content.token\" } ], \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]} ] }, { \"name\" : \"/api/users/1000\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"method\" : \"POST\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"$token\" }, \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }}, \"validate\" : [ { \"eq\" : [ \"status_code\" , 201 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]}, { \"eq\" : [ \"content.msg\" , \"user created successfully.\" ]} ] } ] }, { ... } # another testcase ] \u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u65f6\uff0c\u652f\u6301\u4f20\u5165\u5355\u4e2a\u7ed3\u6784\u4f53\uff08dict\uff09\uff0c\u6216\u8005\u591a\u4e2a\u7ed3\u6784\u4f53\uff08list of dict\uff09\u3002 # \u8fd0\u884c\u5355\u4e2a\u7ed3\u6784\u4f53 runner . run ( testcase ) # \u8fd0\u884c\u591a\u4e2a\u7ed3\u6784\u4f53 runner . run ([ testcase1 , testcase2 ])","title":"\u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53"},{"location":"development/dev-api/#debugtalkpy-env","text":"\b\u901a\u8fc7\u4f20\u5165\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u65f6\uff0c\bHttpRunner \u4f1a\u81ea\u52a8\u4ee5\b\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u4e3a\u8d77\u70b9\uff0c\u5411\u4e0a\u641c\u7d22 debugtalk.py \u6587\u4ef6\uff0c\u5e76\u5c06 debugtalk.py \u6587\u4ef6\u6240\u5728\u7684\u6587\u4ef6\u76ee\u5f55\u4f5c\u4e3a\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u3002 \u540c\u65f6\uff0cHttpRunner \u4f1a\u5728\u5f53\u524d\u5de5\u4f5c\u76ee\u5f55\uff08PWD\uff09\u4e0b\b\b\u641c\u7d22 .env \u6587\u4ef6\uff0c\u4ee5\u53ca api \u548c testcases \u6587\u4ef6\u5939\uff0c\u5e76\u81ea\u52a8\u8fdb\u884c\u52a0\u8f7d\u3002 \u6700\u7ec8\u52a0\u8f7d\u5f97\u5230\u7684\u5b58\u50a8\u7ed3\u6784\u5982\u4e0b\u6240\u793a\uff1a { \"env\" : {}, \"debugtalk\" : { \"variables\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"functions\" : { \"gen_random_string\" : , \"get_sign\" : , \"get_user_id\" : , \"get_account\" : , \"get_os_platform\" : } }, \"def-api\" : {}, \"def-testcase\" : {} } \u5176\u4e2d\uff0c env \u5bf9\u5e94\u7684\u662f .env \u6587\u4ef6\u4e2d\u7684\u73af\u5883\u53d8\u91cf\uff0c debugtalk \u5bf9\u5e94\u7684\u662f debugtalk.py \u6587\u4ef6\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\u548c\u51fd\u6570\uff0c def-api \u5bf9\u5e94\u7684\u662f api \u6587\u4ef6\u5939\u4e0b\u5b9a\u4e49\u7684\u63a5\u53e3\u63cf\u8ff0\uff0c def-testcase \u5bf9\u5e94\u7684\u662f testcases \u6587\u4ef6\u5939\u4e0b\u5b9a\u4e49\u7684\u6d4b\u8bd5\u7528\u4f8b\u3002 \u901a\u8fc7\u4f20\u5165\u6807\u51c6\u7684\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784\u4f53\u6267\u884c\u6d4b\u8bd5\u65f6\uff0c\u4f20\u5165\u7684\u6570\u636e\u5e94\u5305\u542b\u6240\u6709\u4fe1\u606f\uff0c\u5305\u62ec debugtalk.py \u3001 .env \u3001\u4f9d\u8d56\u7684 api \u548c \u6d4b\u8bd5\u7528\u4f8b\u7b49\uff1b\u56e0\u6b64\u4e5f\u65e0\u9700\u518d\u4f7f\u7528 dot_env_path \u548c mapping \u53c2\u6570\uff0c\u6240\u6709\u4fe1\u606f\u90fd\u8981\u901a\u8fc7 refs \u4f20\u5165\u3002","title":"\u52a0\u8f7d debugtalk.py && .env"},{"location":"development/dev-api/#_8","text":"\u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u901a\u8fc7 summary \u5c5e\u6027\u53ef\u83b7\u53d6\u8be6\u5c3d\u7684\u8fd0\u884c\u7ed3\u679c\u6570\u636e\u3002 # get result summary summary = runner . summary \u5176\u6570\u636e\u7ed3\u6784\u4e3a\uff1a { \"success\" : true , \"stat\" : { \"testsRun\" : 2 , \"failures\" : 0 , \"errors\" : 0 , \"skipped\" : 0 , \"expectedFailures\" : 0 , \"unexpectedSuccesses\" : 0 , \"successes\" : 2 }, \"time\" : { \"start_at\" : 1538449655.944404 , \"duration\" : 0.03181314468383789 }, \"platform\" : { \"httprunner_version\" : \"1.5.14\" , \"python_version\" : \"CPython 3.6.5+\" , \"platform\" : \"Darwin-17.6.0-x86_64-i386-64bit\" }, \"details\" : [ { \"success\" : true , \"name\" : \"testcase description\" , \"base_url\" : \"\" , \"stat\" : { \"testsRun\" : 2 , \"failures\" : 0 , \"errors\" : 0 , \"skipped\" : 0 , \"expectedFailures\" : 0 , \"unexpectedSuccesses\" : 0 , \"successes\" : 2 }, \"time\" : { \"start_at\" : 1538449655.944404 , \"duration\" : 0.03181314468383789 }, \"records\" : [ { \"name\" : \"/api/get-token\" , \"status\" : \"success\" , \"attachment\" : \"\" , \"meta_data\" : { \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" , \"Accept-Encoding\" : \"gzip, deflate\" , \"Accept\" : \"*/*\" , \"Connection\" : \"keep-alive\" , \"Content-Type\" : \"application/json\" , \"app_version\" : \"2.8.6\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"os_platform\" : \"ios\" , \"user_agent\" : \"iOS/10.3\" , \"Content-Length\" : \"52\" }, \"start_timestamp\" : 1538449655.944801 , \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" }, \"body\" : b' { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" } ' }, \"response\" : { \"status_code\" : 200 , \"headers\" : { \"Content-Type\" : \"application/json\" , \"Content-Length\" : \"46\" , \"Server\" : \"Werkzeug/0.14.1 Python/3.6.5+\" , \"Date\" : \"Tue, 02 Oct 2018 03:07:35 GMT\" }, \"content_size\" : 46 , \"response_time_ms\" : 12.87 , \"elapsed_ms\" : 6.955 , \"encoding\" : null , \"content\" : b' { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } ' , \"content_type\" : \"application/json\" , \"ok\" : true , \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"reason\" : \"OK\" , \"cookies\" : {}, \"text\" : ' { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } ' , \"json\" : { \"success\" : true , \"token\" : \"CcQ7dBjZZbjIXRkG\" } }, \"validators\" : [ { \"check\" : \"status_code\" , \"expect\" : 200 , \"comparator\" : \"eq\" , \"check_value\" : 200 , \"check_result\" : \"pass\" }, { \"check\" : \"headers.Content-Type\" , \"expect\" : \"application/json\" , \"comparator\" : \"eq\" , \"check_value\" : \"application/json\" , \"check_result\" : \"pass\" }, { \"check\" : \"content.success\" , \"expect\" : true , \"comparator\" : \"eq\" , \"check_value\" : true , \"check_result\" : \"pass\" } ] } }, { \"name\" : \"/api/users/1000\" , \"status\" : \"success\" , \"attachment\" : \"\" , \"meta_data\" : { \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"method\" : \"POST\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" , \"Accept-Encoding\" : \"gzip, deflate\" , \"Accept\" : \"*/*\" , \"Connection\" : \"keep-alive\" , \"Content-Type\" : \"application/json\" , \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"CcQ7dBjZZbjIXRkG\" , \"Content-Length\" : \"39\" }, \"start_timestamp\" : 1538449655.958944 , \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }, \"body\" : b' { \"name\" : \"user1\" , \"password\" : \"123456\" } ' }, \"response\" : { \"status_code\" : 201 , \"headers\" : { \"Content-Type\" : \"application/json\" , \"Content-Length\" : \"54\" , \"Server\" : \"Werkzeug/0.14.1 Python/3.6.5+\" , \"Date\" : \"Tue, 02 Oct 2018 03:07:35 GMT\" }, \"content_size\" : 54 , \"response_time_ms\" : 3.34 , \"elapsed_ms\" : 2.16 , \"encoding\" : null , \"content\" : b' { \"success\" : true , \"msg\" : \"user created successfully.\" } ' , \"content_type\" : \"application/json\" , \"ok\" : true , \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"reason\" : \"CREATED\" , \"cookies\" : {}, \"text\" : ' { \"success\" : true , \"msg\" : \"user created successfully.\" } ' , \"json\" : { \"success\" : true , \"msg\" : \"user created successfully.\" } }, \"validators\" : [ { \"check\" : \"status_code\" , \"expect\" : 201 , \"comparator\" : \"eq\" , \"check_value\" : 201 , \"check_result\" : \"pass\" }, { \"check\" : \"headers.Content-Type\" , \"expect\" : \"application/json\" , \"comparator\" : \"eq\" , \"check_value\" : \"application/json\" , \"check_result\" : \"pass\" }, { \"check\" : \"content.success\" , \"expect\" : true , \"comparator\" : \"eq\" , \"check_value\" : true , \"check_result\" : \"pass\" }, { \"check\" : \"content.msg\" , \"expect\" : \"user created successfully.\" , \"comparator\" : \"eq\" , \"check_value\" : \"user created successfully.\" , \"check_result\" : \"pass\" } ] } } ], \"in_out\" : { \"in\" : { \"SECRET_KEY\" : \"DebugTalk\" }, \"out\" : { \"token\" : \"CcQ7dBjZZbjIXRkG\" } } } ] }","title":"\u8fd4\u56de\u8be6\u7ec6\u6d4b\u8bd5\u7ed3\u679c\u6570\u636e"},{"location":"development/dev-api/#html","text":"\u5982\u9700\u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a\uff0c\u53ef\u8c03\u7528 gen_html_report \u65b9\u6cd5\u3002 # generate html report runner . gen_html_report ( html_report_name = \"demo\" , html_report_template = \"/path/to/custom_report_template\" ) # => reports/demo/demo-1532078874.html \u5173\u4e8e\u6d4b\u8bd5\u62a5\u544a\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u8bf7\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u90e8\u5206\u3002","title":"\u751f\u6210 HTML \u6d4b\u8bd5\u62a5\u544a"},{"location":"examples/testerhome-login/","text":"\u6848\u4f8b\u4ecb\u7ecd \u00b6 \u901a\u8fc7\u63a5\u53e3\u81ea\u52a8\u5316\u5b9e\u73b0 TesterHome \u7684\u767b\u5f55\u9000\u51fa\u529f\u80fd\u3002 \u529f\u80fd\u63cf\u8ff0\uff1a \u8fdb\u5165 \u767b\u5f55\u9875\u9762 \u8f93\u5165\u8d26\u53f7\u548c\u5bc6\u7801 \u70b9\u51fb\u3010Sign In\u3011\u8fdb\u884c\u767b\u5f55 \u51c6\u5907\u5de5\u4f5c \u00b6 \u6293\u5305\u751f\u6210 HAR \u6587\u4ef6 \u00b6 \u5728\u6d4f\u89c8\u5668\u4e2d\u4eba\u5de5\u8fdb\u884c\u767b\u5f55\u64cd\u4f5c\uff0c\u540c\u65f6\u4f7f\u7528\u6293\u5305\u5de5\u5177\u8fdb\u884c\u6293\u5305\u3002\u6293\u5305\u65f6\u5efa\u8bae\u4f7f\u7528\u8fc7\u6ee4\u5668\uff08Filter\uff09\uff0c\u5e38\u7528\u7684\u505a\u6cd5\u662f\u91c7\u7528\u88ab\u6d4b\u7cfb\u7edf\u7684 host\uff0c\u5c06\u65e0\u5173\u8bf7\u6c42\u8fc7\u6ee4\u6389\u3002 \u9009\u62e9\u9700\u8981\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u7684\u8bf7\u6c42\uff0c\u5bfc\u51fa\u4e3a HTTP Archive (.har) \u683c\u5f0f\u7684\u6587\u4ef6\u3002 \u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u6210\u529f\u5b89\u88c5 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e har2case \u547d\u4ee4\uff0c\u4f7f\u7528\u8be5\u547d\u4ee4\u53ef\u5c06 HAR \u6570\u636e\u5305\u8f6c\u6362\u4e3a HttpRunner \u652f\u6301\u7684 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 $ har2case docs/data/testerhome-login.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/testerhome-login.yml \u751f\u6210\u7684\u6d4b\u8bd5\u7528\u4f8b\u5185\u5bb9\u5982\u4e0b\uff1a \u70b9\u51fb\u67e5\u770b - config : name : testcase description variables : {} - test : name : /account/sign_in request : headers : If-None-Match : W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\" User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] - test : name : /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png request : headers : User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , image/png ] - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA== X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/javascript; charset=utf-8 ] - test : name : / request : headers : If-None-Match : W/\"bad62c68dac27b01151516aad5c7f0be\" Turbolinks-Referrer : https://testerhome.com/account/sign_in User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/ validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] \u9996\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u6210\u529f\u5b89\u88c5 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e hrun \u547d\u4ee4\uff0c\u8be5\u547d\u4ee4\u662f HttpRunner \u7684\u6838\u5fc3\u547d\u4ee4\uff0c\u7528\u4e8e\u8fd0\u884c HttpRunner \u652f\u6301\u7684 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 \u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5148\u5c1d\u8bd5\u8fd0\u884c\u4e00\u6b21\uff0c\u5927\u591a\u6570\u60c5\u51b5\uff0c\u5982\u679c\u88ab\u6d4b\u573a\u666f\u4e2d\u4e0d\u5b58\u5728\u5173\u8054\u7684\u60c5\u51b5\uff0c\u662f\u53ef\u4ee5\u76f4\u63a5\u8fd0\u884c\u6210\u529f\u7684\u3002 $ hrun docs/data/testerhome-login.yml --failfast --log-level info INFO Start to run testcase: testcase description /account/sign_in INFO GET https://testerhome.com/account/sign_in INFO status_code: 200 , response_time ( ms ) : 189 .66 ms, response_length: 12584 bytes . /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png INFO GET https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png INFO status_code: 200 , response_time ( ms ) : 83 .98 ms, response_length: 15229 bytes . /account/sign_in INFO POST https://testerhome.com/account/sign_in INFO status_code: 200 , response_time ( ms ) : 172 .8 ms, response_length: 89 bytes . / INFO GET https://testerhome.com/ INFO status_code: 200 , response_time ( ms ) : 257 .41 ms, response_length: 52463 bytes . ---------------------------------------------------------------------- Ran 4 tests in 0 .722s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1555662601.html \u6bd4\u8f83\u5e78\u8fd0\uff0c\u811a\u672c\u5728\u6ca1\u6709\u505a\u4efb\u4f55\u4fee\u6539\u7684\u60c5\u51b5\u4e0b\u8fd0\u884c\u6210\u529f\u4e86\u3002 \u8c03\u8bd5 & \u4f18\u5316\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u867d\u7136\u811a\u672c\u8fd0\u884c\u6210\u529f\u4e86\uff0c\u4f46\u662f\u4e3a\u4e86\u66f4\u597d\u5730\u7ba1\u7406\u548c\u7ef4\u62a4\u811a\u672c\uff0c\u9700\u8981\u5bf9\u811a\u672c\u8fdb\u884c\u4f18\u5316\u8c03\u6574\u3002 \u5173\u8054\u5904\u7406 \u00b6 \u67e5\u770b\u5f55\u5236\u751f\u6210\u7684\u811a\u672c\uff0c\u53ef\u4ee5\u770b\u5230\u5728\u53d1\u8d77\u767b\u5f55\u8bf7\u6c42\u65f6\u5305\u542b\u4e86 X-CSRF-Token \uff0c\u5982\u679c\u719f\u6089\u7f51\u7edc\u4fe1\u606f\u5b89\u5168\u7684\u57fa\u7840\u77e5\u8bc6\uff0c\u5c31\u4f1a\u8054\u60f3\u5230\u8be5\u5b57\u6bb5\u662f\u52a8\u6001\u53d8\u5316\u7684\uff0c\u6bcf\u6b21\u90fd\u662f\u5148\u4ece\u670d\u52a1\u5668\u7aef\u8fd4\u56de\u81f3\u5ba2\u6237\u7aef\uff0c\u5ba2\u6237\u7aef\u5728\u540e\u7eed\u53d1\u8d77\u8bf7\u6c42\u65f6\u9700\u8981\u643a\u5e26\u8be5\u5b57\u6bb5\u3002 - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA== X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] \u867d\u7136\u5f53\u524d\u76f4\u63a5\u8fd0\u884c\u5f55\u5236\u751f\u6210\u7684\u811a\u672c\u4e5f\u662f\u6210\u529f\u7684\uff0c\u4f46\u5f88\u6709\u53ef\u80fd\u5728\u8fc7\u4e86\u4e00\u6bb5\u65f6\u95f4\u540e\uff0c X-CSRF-Token \u5931\u6548\uff0c\u811a\u672c\u4e5f\u5c31\u65e0\u6cd5\u518d\u6210\u529f\u8fd0\u884c\u4e86\u3002\u56e0\u6b64\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\uff0c\u8be5\u5b57\u6bb5\u4e0d\u80fd\u5199\u6b7b\u4e3a\u6293\u5305\u65f6\u83b7\u53d6\u7684\u503c\uff0c\u800c\u662f\u8981\u6bcf\u6b21\u52a8\u6001\u5730\u4ece\u524d\u9762\u7684\u63a5\u53e3\u54cd\u5e94\u4e2d\u83b7\u53d6\u3002 \u90a3\u8981\u600e\u4e48\u786e\u5b9a\u8be5\u5b57\u6bb5\u662f\u5728\u4e4b\u524d\u7684\u54ea\u4e2a\u63a5\u53e3\u4e2d\u8fd4\u56de\u7684\u5462\uff1f \u64cd\u4f5c\u65b9\u5f0f\u4e5f\u5f88\u7b80\u5355\uff0c\u53ef\u4ee5\u5728\u6293\u5305\u5de5\u5177\u4e2d\u5bf9\u8be5\u5b57\u6bb5\u8fdb\u884c\u641c\u7d22\uff0c\u7279\u522b\u5730\uff0c\u641c\u7d22\u8303\u56f4\u9650\u5b9a\u4e3a\u54cd\u5e94\u5185\u5bb9\uff08Response Header\u3001Response Body\uff09\u3002 \u5373\u53ef\u641c\u7d22\u5f97\u5230\u8be5\u5b57\u6bb5\u662f\u5728\u54ea\u4e2a\u63a5\u53e3\u4e2d\u4ece\u670d\u52a1\u5668\u7aef\u8fd4\u56de\u503c\u5ba2\u6237\u7aef\u7684\u3002 \u6709\u65f6\u5019\u53ef\u80fd\u641c\u7d22\u4f1a\u5f97\u5230\u591a\u4e2a\u7ed3\u679c\uff0c\u90a3\u4e48\u5728\u786e\u5b9a\u662f\u4f7f\u7528\u54ea\u4e2a\u63a5\u53e3\u54cd\u5e94\u7684\u65f6\u5019\uff0c\u9075\u5faa\u4e24\u4e2a\u539f\u5219\u5373\u53ef\uff1a \u54cd\u5e94\u4e00\u5b9a\u662f\u51fa\u73b0\u5728\u5f53\u524d\u63a5\u53e3\u4e4b\u524d \u5982\u679c\u5728\u5f53\u524d\u63a5\u53e3\u4e4b\u524d\u5b58\u5728\u591a\u4e2a\u63a5\u53e3\u5747\u6709\u6b64\u8fd4\u56de\uff0c\u90a3\u4e48\u53d6\u6700\u9760\u8fd1\u5f53\u524d\u63a5\u53e3\u7684\u5373\u53ef \u901a\u8fc7\u524d\u9762\u7684\u641c\u7d22\u53ef\u77e5\uff0c X-CSRF-Token \u7684\u503c\u662f\u5728\u7b2c\u4e00\u4e2a\u63a5\u53e3\u4e2d\u54cd\u5e94\u8fd4\u56de\u7684\u3002 \u786e\u5b9a\u51fa\u5177\u4f53\u7684\u63a5\u53e3\u540e\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\u4ece\u8be5\u63a5\u53e3\u4f7f\u7528 extract \u63d0\u53d6\u5bf9\u5e94\u7684\u5b57\u6bb5\uff0c\u7136\u540e\u5728\u540e\u7eed\u63a5\u53e3\u4e2d\u5f15\u7528\u63d0\u53d6\u51fa\u7684\u5b57\u6bb5\u3002 \u5728\u5f53\u524d\u6848\u4f8b\u4e2d\uff0c\u7b2c\u4e00\u4e2a\u63a5\u53e3\u7684\u54cd\u5e94\u5185\u5bb9\u4e3a HTML \u9875\u9762\uff0c\u8981\u63d0\u53d6\u5b57\u6bb5\u53ef\u4ee5\u4f7f\u7528\u6b63\u5219\u5339\u914d\u7684\u65b9\u5f0f\u3002\u5177\u4f53\u7684\u505a\u6cd5\u5c31\u662f\u6307\u5b9a\u76ee\u6807\u5b57\u6bb5\u7684\u5de6\u53f3\u8fb9\u754c\uff0c\u76ee\u6807\u5b57\u6bb5\u4f7f\u7528 (.*) \u5339\u914d\u83b7\u53d6\u3002 - test : name : /account/sign_in request : headers : If-None-Match : W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\" User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/account/sign_in extract : X_CSRF_Token : validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] \u7136\u540e\uff0c\u5728\u540e\u7eed\u4f7f\u7528\u5230\u8be5\u5b57\u6bb5\u7684\u63a5\u53e3\u4e2d\uff0c\u5f15\u7528\u63d0\u53d6\u51fa\u7684\u5b57\u6bb5\u5373\u53ef\u3002 - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : $X_CSRF_Token X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ]","title":"TesterHome \u767b\u5f55"},{"location":"examples/testerhome-login/#_1","text":"\u901a\u8fc7\u63a5\u53e3\u81ea\u52a8\u5316\u5b9e\u73b0 TesterHome \u7684\u767b\u5f55\u9000\u51fa\u529f\u80fd\u3002 \u529f\u80fd\u63cf\u8ff0\uff1a \u8fdb\u5165 \u767b\u5f55\u9875\u9762 \u8f93\u5165\u8d26\u53f7\u548c\u5bc6\u7801 \u70b9\u51fb\u3010Sign In\u3011\u8fdb\u884c\u767b\u5f55","title":"\u6848\u4f8b\u4ecb\u7ecd"},{"location":"examples/testerhome-login/#_2","text":"","title":"\u51c6\u5907\u5de5\u4f5c"},{"location":"examples/testerhome-login/#har","text":"\u5728\u6d4f\u89c8\u5668\u4e2d\u4eba\u5de5\u8fdb\u884c\u767b\u5f55\u64cd\u4f5c\uff0c\u540c\u65f6\u4f7f\u7528\u6293\u5305\u5de5\u5177\u8fdb\u884c\u6293\u5305\u3002\u6293\u5305\u65f6\u5efa\u8bae\u4f7f\u7528\u8fc7\u6ee4\u5668\uff08Filter\uff09\uff0c\u5e38\u7528\u7684\u505a\u6cd5\u662f\u91c7\u7528\u88ab\u6d4b\u7cfb\u7edf\u7684 host\uff0c\u5c06\u65e0\u5173\u8bf7\u6c42\u8fc7\u6ee4\u6389\u3002 \u9009\u62e9\u9700\u8981\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u7684\u8bf7\u6c42\uff0c\u5bfc\u51fa\u4e3a HTTP Archive (.har) \u683c\u5f0f\u7684\u6587\u4ef6\u3002","title":"\u6293\u5305\u751f\u6210 HAR \u6587\u4ef6"},{"location":"examples/testerhome-login/#_3","text":"\u6210\u529f\u5b89\u88c5 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e har2case \u547d\u4ee4\uff0c\u4f7f\u7528\u8be5\u547d\u4ee4\u53ef\u5c06 HAR \u6570\u636e\u5305\u8f6c\u6362\u4e3a HttpRunner \u652f\u6301\u7684 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 $ har2case docs/data/testerhome-login.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/testerhome-login.yml \u751f\u6210\u7684\u6d4b\u8bd5\u7528\u4f8b\u5185\u5bb9\u5982\u4e0b\uff1a \u70b9\u51fb\u67e5\u770b - config : name : testcase description variables : {} - test : name : /account/sign_in request : headers : If-None-Match : W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\" User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] - test : name : /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png request : headers : User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , image/png ] - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA== X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/javascript; charset=utf-8 ] - test : name : / request : headers : If-None-Match : W/\"bad62c68dac27b01151516aad5c7f0be\" Turbolinks-Referrer : https://testerhome.com/account/sign_in User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/ validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ]","title":"\u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b"},{"location":"examples/testerhome-login/#_4","text":"\u6210\u529f\u5b89\u88c5 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e hrun \u547d\u4ee4\uff0c\u8be5\u547d\u4ee4\u662f HttpRunner \u7684\u6838\u5fc3\u547d\u4ee4\uff0c\u7528\u4e8e\u8fd0\u884c HttpRunner \u652f\u6301\u7684 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 \u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5148\u5c1d\u8bd5\u8fd0\u884c\u4e00\u6b21\uff0c\u5927\u591a\u6570\u60c5\u51b5\uff0c\u5982\u679c\u88ab\u6d4b\u573a\u666f\u4e2d\u4e0d\u5b58\u5728\u5173\u8054\u7684\u60c5\u51b5\uff0c\u662f\u53ef\u4ee5\u76f4\u63a5\u8fd0\u884c\u6210\u529f\u7684\u3002 $ hrun docs/data/testerhome-login.yml --failfast --log-level info INFO Start to run testcase: testcase description /account/sign_in INFO GET https://testerhome.com/account/sign_in INFO status_code: 200 , response_time ( ms ) : 189 .66 ms, response_length: 12584 bytes . /assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png INFO GET https://testerhome.com/assets/big_logo-cd32144f74c18746f3dce33e1040e7dfe4c07c8e611e37f3868b1c16b5095da3.png INFO status_code: 200 , response_time ( ms ) : 83 .98 ms, response_length: 15229 bytes . /account/sign_in INFO POST https://testerhome.com/account/sign_in INFO status_code: 200 , response_time ( ms ) : 172 .8 ms, response_length: 89 bytes . / INFO GET https://testerhome.com/ INFO status_code: 200 , response_time ( ms ) : 257 .41 ms, response_length: 52463 bytes . ---------------------------------------------------------------------- Ran 4 tests in 0 .722s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1555662601.html \u6bd4\u8f83\u5e78\u8fd0\uff0c\u811a\u672c\u5728\u6ca1\u6709\u505a\u4efb\u4f55\u4fee\u6539\u7684\u60c5\u51b5\u4e0b\u8fd0\u884c\u6210\u529f\u4e86\u3002","title":"\u9996\u6b21\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b"},{"location":"examples/testerhome-login/#_5","text":"\u867d\u7136\u811a\u672c\u8fd0\u884c\u6210\u529f\u4e86\uff0c\u4f46\u662f\u4e3a\u4e86\u66f4\u597d\u5730\u7ba1\u7406\u548c\u7ef4\u62a4\u811a\u672c\uff0c\u9700\u8981\u5bf9\u811a\u672c\u8fdb\u884c\u4f18\u5316\u8c03\u6574\u3002","title":"\u8c03\u8bd5 & \u4f18\u5316\u6d4b\u8bd5\u7528\u4f8b"},{"location":"examples/testerhome-login/#_6","text":"\u67e5\u770b\u5f55\u5236\u751f\u6210\u7684\u811a\u672c\uff0c\u53ef\u4ee5\u770b\u5230\u5728\u53d1\u8d77\u767b\u5f55\u8bf7\u6c42\u65f6\u5305\u542b\u4e86 X-CSRF-Token \uff0c\u5982\u679c\u719f\u6089\u7f51\u7edc\u4fe1\u606f\u5b89\u5168\u7684\u57fa\u7840\u77e5\u8bc6\uff0c\u5c31\u4f1a\u8054\u60f3\u5230\u8be5\u5b57\u6bb5\u662f\u52a8\u6001\u53d8\u5316\u7684\uff0c\u6bcf\u6b21\u90fd\u662f\u5148\u4ece\u670d\u52a1\u5668\u7aef\u8fd4\u56de\u81f3\u5ba2\u6237\u7aef\uff0c\u5ba2\u6237\u7aef\u5728\u540e\u7eed\u53d1\u8d77\u8bf7\u6c42\u65f6\u9700\u8981\u643a\u5e26\u8be5\u5b57\u6bb5\u3002 - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : 0zAKFDDPnNI2+Vwq/iwDPR9vo7KWobfNLAye4EaGBTlsSxMzTNf39lLF9z35f5mcROM7JgOP+azBCuDe84G+XA== X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] \u867d\u7136\u5f53\u524d\u76f4\u63a5\u8fd0\u884c\u5f55\u5236\u751f\u6210\u7684\u811a\u672c\u4e5f\u662f\u6210\u529f\u7684\uff0c\u4f46\u5f88\u6709\u53ef\u80fd\u5728\u8fc7\u4e86\u4e00\u6bb5\u65f6\u95f4\u540e\uff0c X-CSRF-Token \u5931\u6548\uff0c\u811a\u672c\u4e5f\u5c31\u65e0\u6cd5\u518d\u6210\u529f\u8fd0\u884c\u4e86\u3002\u56e0\u6b64\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\uff0c\u8be5\u5b57\u6bb5\u4e0d\u80fd\u5199\u6b7b\u4e3a\u6293\u5305\u65f6\u83b7\u53d6\u7684\u503c\uff0c\u800c\u662f\u8981\u6bcf\u6b21\u52a8\u6001\u5730\u4ece\u524d\u9762\u7684\u63a5\u53e3\u54cd\u5e94\u4e2d\u83b7\u53d6\u3002 \u90a3\u8981\u600e\u4e48\u786e\u5b9a\u8be5\u5b57\u6bb5\u662f\u5728\u4e4b\u524d\u7684\u54ea\u4e2a\u63a5\u53e3\u4e2d\u8fd4\u56de\u7684\u5462\uff1f \u64cd\u4f5c\u65b9\u5f0f\u4e5f\u5f88\u7b80\u5355\uff0c\u53ef\u4ee5\u5728\u6293\u5305\u5de5\u5177\u4e2d\u5bf9\u8be5\u5b57\u6bb5\u8fdb\u884c\u641c\u7d22\uff0c\u7279\u522b\u5730\uff0c\u641c\u7d22\u8303\u56f4\u9650\u5b9a\u4e3a\u54cd\u5e94\u5185\u5bb9\uff08Response Header\u3001Response Body\uff09\u3002 \u5373\u53ef\u641c\u7d22\u5f97\u5230\u8be5\u5b57\u6bb5\u662f\u5728\u54ea\u4e2a\u63a5\u53e3\u4e2d\u4ece\u670d\u52a1\u5668\u7aef\u8fd4\u56de\u503c\u5ba2\u6237\u7aef\u7684\u3002 \u6709\u65f6\u5019\u53ef\u80fd\u641c\u7d22\u4f1a\u5f97\u5230\u591a\u4e2a\u7ed3\u679c\uff0c\u90a3\u4e48\u5728\u786e\u5b9a\u662f\u4f7f\u7528\u54ea\u4e2a\u63a5\u53e3\u54cd\u5e94\u7684\u65f6\u5019\uff0c\u9075\u5faa\u4e24\u4e2a\u539f\u5219\u5373\u53ef\uff1a \u54cd\u5e94\u4e00\u5b9a\u662f\u51fa\u73b0\u5728\u5f53\u524d\u63a5\u53e3\u4e4b\u524d \u5982\u679c\u5728\u5f53\u524d\u63a5\u53e3\u4e4b\u524d\u5b58\u5728\u591a\u4e2a\u63a5\u53e3\u5747\u6709\u6b64\u8fd4\u56de\uff0c\u90a3\u4e48\u53d6\u6700\u9760\u8fd1\u5f53\u524d\u63a5\u53e3\u7684\u5373\u53ef \u901a\u8fc7\u524d\u9762\u7684\u641c\u7d22\u53ef\u77e5\uff0c X-CSRF-Token \u7684\u503c\u662f\u5728\u7b2c\u4e00\u4e2a\u63a5\u53e3\u4e2d\u54cd\u5e94\u8fd4\u56de\u7684\u3002 \u786e\u5b9a\u51fa\u5177\u4f53\u7684\u63a5\u53e3\u540e\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728\u6d4b\u8bd5\u811a\u672c\u4e2d\u4ece\u8be5\u63a5\u53e3\u4f7f\u7528 extract \u63d0\u53d6\u5bf9\u5e94\u7684\u5b57\u6bb5\uff0c\u7136\u540e\u5728\u540e\u7eed\u63a5\u53e3\u4e2d\u5f15\u7528\u63d0\u53d6\u51fa\u7684\u5b57\u6bb5\u3002 \u5728\u5f53\u524d\u6848\u4f8b\u4e2d\uff0c\u7b2c\u4e00\u4e2a\u63a5\u53e3\u7684\u54cd\u5e94\u5185\u5bb9\u4e3a HTML \u9875\u9762\uff0c\u8981\u63d0\u53d6\u5b57\u6bb5\u53ef\u4ee5\u4f7f\u7528\u6b63\u5219\u5339\u914d\u7684\u65b9\u5f0f\u3002\u5177\u4f53\u7684\u505a\u6cd5\u5c31\u662f\u6307\u5b9a\u76ee\u6807\u5b57\u6bb5\u7684\u5de6\u53f3\u8fb9\u754c\uff0c\u76ee\u6807\u5b57\u6bb5\u4f7f\u7528 (.*) \u5339\u914d\u83b7\u53d6\u3002 - test : name : /account/sign_in request : headers : If-None-Match : W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\" User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 method : GET url : https://testerhome.com/account/sign_in extract : X_CSRF_Token : validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ] \u7136\u540e\uff0c\u5728\u540e\u7eed\u4f7f\u7528\u5230\u8be5\u5b57\u6bb5\u7684\u63a5\u53e3\u4e2d\uff0c\u5f15\u7528\u63d0\u53d6\u51fa\u7684\u5b57\u6bb5\u5373\u53ef\u3002 - test : name : /account/sign_in request : data : commit : Sign In user[login] : debugtalk user[password] : XXXXXXXX user[remember_me] : '1' utf8 : \u2713 headers : Content-Type : application/x-www-form-urlencoded; charset=UTF-8 User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 X-CSRF-Token : $X_CSRF_Token X-Requested-With : XMLHttpRequest method : POST url : https://testerhome.com/account/sign_in validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , text/html; charset=utf-8 ]","title":"\u5173\u8054\u5904\u7406"},{"location":"prepare/dot-env/","text":"\u73af\u5883\u53d8\u91cf\u7684\u4f5c\u7528 \u00b6 \u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u4e2d\uff0c\u6709\u65f6\u9700\u8981\u501f\u52a9\u73af\u5883\u53d8\u91cf\u5b9e\u73b0\u67d0\u4e9b\u7279\u5b9a\u7684\u76ee\u7684\uff0c\u5e38\u89c1\u7684\u573a\u666f\u5305\u62ec\uff1a \u5207\u6362\u6d4b\u8bd5\u73af\u5883 \u5207\u6362\u6d4b\u8bd5\u914d\u7f6e \u5b58\u50a8\u654f\u611f\u6570\u636e\uff08\u4ece \u4fe1\u606f\u5b89\u5168 \u7684\u89d2\u5ea6\u51fa\u53d1\uff09 \u8bbe\u7f6e\u73af\u5883\u53d8\u91cf \u00b6 \u5728\u7ec8\u7aef\u4e2d\u9884\u8bbe\u73af\u5883\u53d8\u91cf \u00b6 \u4f7f\u7528\u73af\u5883\u53d8\u91cf\u4e4b\u524d\uff0c\u9700\u8981\u5148\u5728\u7cfb\u7edf\u4e2d\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf\u540d\u79f0\u548c\u503c\uff0c\u4f20\u7edf\u7684\u65b9\u5f0f\u4e3a\u4f7f\u7528 export \u547d\u4ee4\uff08Windows\u7cfb\u7edf\u4e2d\u4f7f\u7528 set \u547d\u4ee4\uff09\uff1a $ export UserName = admin $ echo $UserName admin $ export Password = 123456 $ echo $Password 123456 \u7136\u540e\uff0c\u5728\u7a0b\u5e8f\u4e2d\u5c31\u53ef\u4ee5\u5bf9\u7cfb\u7edf\u4e2d\u7684\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u8bfb\u53d6\u3002 $ python >>> import os >>> os.environ [ \"UserName\" ] 'admin' \u901a\u8fc7 .env \u6587\u4ef6\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf \u00b6 \u9664\u4e86\u8fd9\u79cd\u65b9\u5f0f\uff0cHttpRunner \u8fd8\u501f\u9274\u4e86 pipenv \u52a0\u8f7d .env \u7684\u65b9\u5f0f \u3002 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e2d\uff0c\u521b\u5efa .env \u6587\u4ef6\uff0c\u5e76\u5c06\u654f\u611f\u6570\u636e\u4fe1\u606f\u653e\u7f6e\u5230\u5176\u4e2d\uff0c\u5b58\u50a8\u91c7\u7528 name=value \u7684\u683c\u5f0f\uff1a $ cat .env UserName = admin Password = 123456 PROJECT_KEY = ABCDEFGH \u540c\u65f6\uff0c .env \u6587\u4ef6\u4e0d\u5e94\u8be5\u6dfb\u52a0\u5230\u4ee3\u7801\u4ed3\u5e93\u4e2d\uff0c\u5efa\u8bae\u5c06 .env \u52a0\u5165\u5230 .gitignore \u4e2d\u3002 HttpRunner \u8fd0\u884c\u65f6\uff0c\u4f1a\u81ea\u52a8\u5c06 .env \u6587\u4ef6\u4e2d\u7684\u5185\u5bb9\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\uff08RunTime\uff09\u7684\u73af\u5883\u53d8\u91cf\u4e2d\uff0c\u7136\u540e\u5728\u8fd0\u884c\u65f6\u4e2d\u5c31\u53ef\u4ee5\u5bf9\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u8bfb\u53d6\u4e86\u3002 \u82e5\u9700\u52a0\u8f7d\u4e0d\u4f4d\u4e8e\u81ea\u52a8\u5316\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u7684 .env \uff0c\u6216\u8005\u5176\u5b83\u540d\u79f0\u7684 .env \u6587\u4ef6\uff08\u4f8b\u5982 production.env \uff09\uff0c\u53ef\u4ee5\u91c7\u7528 --dot-env-path \u53c2\u6570\u6307\u5b9a\u6587\u4ef6\u8def\u5f84\uff1a $ hrun /path/to/testcase.yml --dot-env-path /path/to/.env --log-level debug INFO Loading environment variables from /path/to/.env DEBUG Loaded variable: UserName DEBUG Loaded variable: Password DEBUG Loaded variable: PROJECT_KEY ... \u5f15\u7528\u73af\u5883\u53d8\u91cf \u00b6 \u5728 HttpRunner \u4e2d\u5185\u7f6e\u4e86\u51fd\u6570 environ \uff08\u7b80\u79f0 ENV \uff09\uff0c\u53ef\u7528\u4e8e\u5728 YAML/JSON \u811a\u672c\u4e2d\u76f4\u63a5\u5f15\u7528\u73af\u5883\u53d8\u91cf\u3002 - test : name : login request : url : http://host/api/login method : POST headers : Content-Type : application/json json : username : ${ENV(UserName)} password : ${ENV(Password)} validate : - eq : [ status_code , 200 ] \u82e5\u8fd8\u9700\u5bf9\u8bfb\u53d6\u7684\u73af\u5883\u53d8\u91cf\u505a\u8fdb\u4e00\u6b65\u5904\u7406\uff0c\u5219\u53ef\u4ee5\u5728 debugtalk.py \u901a\u8fc7 Python \u5185\u7f6e\u7684\u51fd\u6570 os.environ \u5bf9\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u5f15\u7528\uff0c\u7136\u540e\u518d\u5b9e\u73b0\u5904\u7406\u903b\u8f91\u3002 \u4f8b\u5982\uff0c\u82e5\u53d1\u8d77\u8bf7\u6c42\u7684\u5bc6\u7801\u9700\u8981\u5148\u4e0e\u5bc6\u94a5\u8fdb\u884c\u62fc\u63a5\u5e76\u751f\u6210 MD5\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728 debugtalk.py \u6587\u4ef6\u4e2d\u5b9e\u73b0\u5982\u4e0b\u51fd\u6570\uff1a import os def get_encrypt_password (): raw_passwd = os . environ [ \"Password\" ] PROJECT_KEY = os . environ [ \"PROJECT_KEY\" ]) password = ( raw_passwd + PROJECT_KEY ) . encode ( 'ascii' ) return hmac . new ( password , hashlib . sha1 ) . hexdigest () \u7136\u540e\uff0c\u5728 YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7 ${func()} \u7684\u65b9\u5f0f\u5f15\u7528\u73af\u5883\u53d8\u91cf\u7684\u503c\u4e86\u3002 - test : name : login request : url : http://host/api/login method : POST headers : Content-Type : application/json json : username : ${ENV(UserName)} password : ${get_encrypt_password()} validate : - eq : [ status_code , 200 ]","title":"\u73af\u5883\u53d8\u91cf"},{"location":"prepare/dot-env/#_1","text":"\u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u4e2d\uff0c\u6709\u65f6\u9700\u8981\u501f\u52a9\u73af\u5883\u53d8\u91cf\u5b9e\u73b0\u67d0\u4e9b\u7279\u5b9a\u7684\u76ee\u7684\uff0c\u5e38\u89c1\u7684\u573a\u666f\u5305\u62ec\uff1a \u5207\u6362\u6d4b\u8bd5\u73af\u5883 \u5207\u6362\u6d4b\u8bd5\u914d\u7f6e \u5b58\u50a8\u654f\u611f\u6570\u636e\uff08\u4ece \u4fe1\u606f\u5b89\u5168 \u7684\u89d2\u5ea6\u51fa\u53d1\uff09","title":"\u73af\u5883\u53d8\u91cf\u7684\u4f5c\u7528"},{"location":"prepare/dot-env/#_2","text":"","title":"\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf"},{"location":"prepare/dot-env/#_3","text":"\u4f7f\u7528\u73af\u5883\u53d8\u91cf\u4e4b\u524d\uff0c\u9700\u8981\u5148\u5728\u7cfb\u7edf\u4e2d\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf\u540d\u79f0\u548c\u503c\uff0c\u4f20\u7edf\u7684\u65b9\u5f0f\u4e3a\u4f7f\u7528 export \u547d\u4ee4\uff08Windows\u7cfb\u7edf\u4e2d\u4f7f\u7528 set \u547d\u4ee4\uff09\uff1a $ export UserName = admin $ echo $UserName admin $ export Password = 123456 $ echo $Password 123456 \u7136\u540e\uff0c\u5728\u7a0b\u5e8f\u4e2d\u5c31\u53ef\u4ee5\u5bf9\u7cfb\u7edf\u4e2d\u7684\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u8bfb\u53d6\u3002 $ python >>> import os >>> os.environ [ \"UserName\" ] 'admin'","title":"\u5728\u7ec8\u7aef\u4e2d\u9884\u8bbe\u73af\u5883\u53d8\u91cf"},{"location":"prepare/dot-env/#env","text":"\u9664\u4e86\u8fd9\u79cd\u65b9\u5f0f\uff0cHttpRunner \u8fd8\u501f\u9274\u4e86 pipenv \u52a0\u8f7d .env \u7684\u65b9\u5f0f \u3002 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e2d\uff0c\u521b\u5efa .env \u6587\u4ef6\uff0c\u5e76\u5c06\u654f\u611f\u6570\u636e\u4fe1\u606f\u653e\u7f6e\u5230\u5176\u4e2d\uff0c\u5b58\u50a8\u91c7\u7528 name=value \u7684\u683c\u5f0f\uff1a $ cat .env UserName = admin Password = 123456 PROJECT_KEY = ABCDEFGH \u540c\u65f6\uff0c .env \u6587\u4ef6\u4e0d\u5e94\u8be5\u6dfb\u52a0\u5230\u4ee3\u7801\u4ed3\u5e93\u4e2d\uff0c\u5efa\u8bae\u5c06 .env \u52a0\u5165\u5230 .gitignore \u4e2d\u3002 HttpRunner \u8fd0\u884c\u65f6\uff0c\u4f1a\u81ea\u52a8\u5c06 .env \u6587\u4ef6\u4e2d\u7684\u5185\u5bb9\u52a0\u8f7d\u5230\u8fd0\u884c\u65f6\uff08RunTime\uff09\u7684\u73af\u5883\u53d8\u91cf\u4e2d\uff0c\u7136\u540e\u5728\u8fd0\u884c\u65f6\u4e2d\u5c31\u53ef\u4ee5\u5bf9\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u8bfb\u53d6\u4e86\u3002 \u82e5\u9700\u52a0\u8f7d\u4e0d\u4f4d\u4e8e\u81ea\u52a8\u5316\u9879\u76ee\u6839\u76ee\u5f55\u4e2d\u7684 .env \uff0c\u6216\u8005\u5176\u5b83\u540d\u79f0\u7684 .env \u6587\u4ef6\uff08\u4f8b\u5982 production.env \uff09\uff0c\u53ef\u4ee5\u91c7\u7528 --dot-env-path \u53c2\u6570\u6307\u5b9a\u6587\u4ef6\u8def\u5f84\uff1a $ hrun /path/to/testcase.yml --dot-env-path /path/to/.env --log-level debug INFO Loading environment variables from /path/to/.env DEBUG Loaded variable: UserName DEBUG Loaded variable: Password DEBUG Loaded variable: PROJECT_KEY ...","title":"\u901a\u8fc7 .env \u6587\u4ef6\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf"},{"location":"prepare/dot-env/#_4","text":"\u5728 HttpRunner \u4e2d\u5185\u7f6e\u4e86\u51fd\u6570 environ \uff08\u7b80\u79f0 ENV \uff09\uff0c\u53ef\u7528\u4e8e\u5728 YAML/JSON \u811a\u672c\u4e2d\u76f4\u63a5\u5f15\u7528\u73af\u5883\u53d8\u91cf\u3002 - test : name : login request : url : http://host/api/login method : POST headers : Content-Type : application/json json : username : ${ENV(UserName)} password : ${ENV(Password)} validate : - eq : [ status_code , 200 ] \u82e5\u8fd8\u9700\u5bf9\u8bfb\u53d6\u7684\u73af\u5883\u53d8\u91cf\u505a\u8fdb\u4e00\u6b65\u5904\u7406\uff0c\u5219\u53ef\u4ee5\u5728 debugtalk.py \u901a\u8fc7 Python \u5185\u7f6e\u7684\u51fd\u6570 os.environ \u5bf9\u73af\u5883\u53d8\u91cf\u8fdb\u884c\u5f15\u7528\uff0c\u7136\u540e\u518d\u5b9e\u73b0\u5904\u7406\u903b\u8f91\u3002 \u4f8b\u5982\uff0c\u82e5\u53d1\u8d77\u8bf7\u6c42\u7684\u5bc6\u7801\u9700\u8981\u5148\u4e0e\u5bc6\u94a5\u8fdb\u884c\u62fc\u63a5\u5e76\u751f\u6210 MD5\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728 debugtalk.py \u6587\u4ef6\u4e2d\u5b9e\u73b0\u5982\u4e0b\u51fd\u6570\uff1a import os def get_encrypt_password (): raw_passwd = os . environ [ \"Password\" ] PROJECT_KEY = os . environ [ \"PROJECT_KEY\" ]) password = ( raw_passwd + PROJECT_KEY ) . encode ( 'ascii' ) return hmac . new ( password , hashlib . sha1 ) . hexdigest () \u7136\u540e\uff0c\u5728 YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7 ${func()} \u7684\u65b9\u5f0f\u5f15\u7528\u73af\u5883\u53d8\u91cf\u7684\u503c\u4e86\u3002 - test : name : login request : url : http://host/api/login method : POST headers : Content-Type : application/json json : username : ${ENV(UserName)} password : ${get_encrypt_password()} validate : - eq : [ status_code , 200 ]","title":"\u5f15\u7528\u73af\u5883\u53d8\u91cf"},{"location":"prepare/parameters/","text":"\u4ecb\u7ecd \u00b6 \u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u4e2d\uff0c\u7ecf\u5e38\u4f1a\u9047\u5230\u5982\u4e0b\u573a\u666f\uff1a \u6d4b\u8bd5\u641c\u7d22\u529f\u80fd\uff0c\u53ea\u6709\u4e00\u4e2a\u641c\u7d22\u8f93\u5165\u6846\uff0c\u4f46\u6709 10 \u79cd\u4e0d\u540c\u7c7b\u578b\u7684\u641c\u7d22\u5173\u952e\u5b57\uff1b \u6d4b\u8bd5\u8d26\u53f7\u767b\u5f55\u529f\u80fd\uff0c\u9700\u8981\u8f93\u5165\u7528\u6237\u540d\u548c\u5bc6\u7801\uff0c\u6309\u7167\u7b49\u4ef7\u7c7b\u5212\u5206\u540e\u6709 20 \u79cd\u7ec4\u5408\u60c5\u51b5\u3002 \u8fd9\u91cc\u53ea\u662f\u968f\u610f\u627e\u4e86\u4e24\u4e2a\u5178\u578b\u7684\u4f8b\u5b50\uff0c\u76f8\u4fe1\u5927\u5bb6\u90fd\u6709\u9047\u5230\u8fc7\u5f88\u591a\u7c7b\u4f3c\u7684\u573a\u666f\u3002\u603b\u7ed3\u4e0b\u6765\uff0c\u5c31\u662f\u5728\u6211\u4eec\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u811a\u672c\u4e2d\u5b58\u5728\u53c2\u6570\uff0c\u5e76\u4e14\u6211\u4eec\u9700\u8981\u91c7\u7528\u4e0d\u540c\u7684\u53c2\u6570\u53bb\u8fd0\u884c\u3002 \u7ecf\u8fc7\u6982\u62ec\uff0c\u53c2\u6570\u57fa\u672c\u4e0a\u5206\u4e3a\u4e24\u79cd\u7c7b\u578b\uff1a \u5355\u4e2a\u72ec\u7acb\u53c2\u6570\uff1a\u4f8b\u5982\u524d\u9762\u7684\u7b2c\u4e00\u79cd\u573a\u666f\uff0c\u6211\u4eec\u53ea\u9700\u8981\u53d8\u6362\u641c\u7d22\u5173\u952e\u5b57\u8fd9\u4e00\u4e2a\u53c2\u6570 \u591a\u4e2a\u5177\u6709\u5173\u8054\u6027\u7684\u53c2\u6570\uff1a\u4f8b\u5982\u524d\u9762\u7684\u7b2c\u4e8c\u79cd\u573a\u666f\uff0c\u6211\u4eec\u9700\u8981\u53d8\u6362\u7528\u6237\u540d\u548c\u5bc6\u7801\u4e24\u4e2a\u53c2\u6570\uff0c\u5e76\u4e14\u8fd9\u4e24\u4e2a\u53c2\u6570\u9700\u8981\u5173\u8054\u7ec4\u5408 \u7136\u540e\uff0c\u5bf9\u4e8e\u53c2\u6570\u800c\u8a00\uff0c\u6211\u4eec\u53ef\u80fd\u5177\u6709\u4e00\u4e2a\u53c2\u6570\u5217\u8868\uff0c\u5728\u811a\u672c\u8fd0\u884c\u65f6\u9700\u8981\u6309\u7167\u4e0d\u540c\u7684\u89c4\u5219\u53bb\u53d6\u503c\uff0c\u4f8b\u5982\u987a\u5e8f\u53d6\u503c\u3001\u968f\u673a\u53d6\u503c\u3001\u5faa\u73af\u53d6\u503c\u7b49\u7b49\u3002 \u8fd9\u5c31\u662f\u5178\u578b\u7684\u53c2\u6570\u5316\u548c\u6570\u636e\u9a71\u52a8\u3002 \u5982\u9700\u4e86\u89e3 HttpRunner \u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u673a\u5236\u7684\u5b9e\u73b0\u539f\u7406\u548c\u6280\u672f\u7ec6\u8282\uff0c\u53ef\u524d\u5f80\u9605\u8bfb \u300aHttpRunner \u5b9e\u73b0\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u673a\u5236\u300b \u3002 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u51c6\u5907 \u00b6 \u4ece 2.0.0 \u7248\u672c\u5f00\u59cb\uff0cHttpRunner \u4e0d\u518d\u652f\u6301\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u8fdb\u884c\u53c2\u6570\u5316\u914d\u7f6e\uff1b\u53c2\u6570\u5316\u7684\u529f\u80fd\u9700\u8981\u5728 testsuite \u4e2d\u5b9e\u73b0\u3002\u53d8\u66f4\u7684\u76ee\u7684\u662f\u8ba9\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u7684\u6982\u5ff5\u66f4\u7eaf\u7cb9\uff0c\u5173\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u548c\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u6982\u5ff5\u5b9a\u4e49\uff0c\u8be6\u89c1 \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u300b \u3002 \u53c2\u6570\u5316\u673a\u5236\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u4e2d\u5b9e\u73b0\u3002\u5982\u9700\u5b9e\u73b0\u6570\u636e\u9a71\u52a8\u673a\u5236\uff0c\u9700\u8981\u521b\u5efa\u4e00\u4e2a testsuite\uff0c\u5728 testsuite \u4e2d\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u5b9a\u4e49\u53c2\u6570\u5316\u914d\u7f6e\u3002 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u7684\u683c\u5f0f\u5982\u4e0b\u6240\u793a\uff1a config : name : testsuite description testcases : testcase1_name : testcase : /path/to/testcase1 testcase2_name : testcase : /path/to/testcase2 \u9700\u8981\u6ce8\u610f\u7684\u662f\uff0ctestsuite \u548c testcase \u7684\u683c\u5f0f\u5b58\u5728\u8f83\u5927\u533a\u522b\uff0c\u8be6\u89c1 \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u300b \u3002 \u53c2\u6570\u914d\u7f6e\u6982\u8ff0 \u00b6 \u5982\u9700\u5bf9\u67d0\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5b9e\u73b0\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u9700\u8981\u4f7f\u7528 parameters \u5173\u952e\u5b57\uff0c\u5b9a\u4e49\u53c2\u6570\u540d\u79f0\u5e76\u6307\u5b9a\u6570\u636e\u6e90\u53d6\u503c\u65b9\u5f0f\u3002 \u53c2\u6570\u540d\u79f0\u7684\u5b9a\u4e49\u5206\u4e3a\u4e24\u79cd\u60c5\u51b5\uff1a \u72ec\u7acb\u53c2\u6570\u5355\u72ec\u8fdb\u884c\u5b9a\u4e49\uff1b \u591a\u4e2a\u53c2\u6570\u5177\u6709\u5173\u8054\u6027\u7684\u53c2\u6570\u9700\u8981\u5c06\u5176\u5b9a\u4e49\u5728\u4e00\u8d77\uff0c\u91c7\u7528\u77ed\u6a2a\u7ebf\uff08 - \uff09\u8fdb\u884c\u8fde\u63a5\u3002 \u6570\u636e\u6e90\u6307\u5b9a\u652f\u6301\u4e09\u79cd\u65b9\u5f0f\uff1a \u5728 YAML/JSON \u4e2d\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\uff1a\u8be5\u79cd\u65b9\u5f0f\u6700\u4e3a\u7b80\u5355\u6613\u7528\uff0c\u9002\u5408\u53c2\u6570\u5217\u8868\u6bd4\u8f83\u5c0f\u7684\u60c5\u51b5 \u901a\u8fc7\u5185\u7f6e\u7684 parameterize\uff08\u53ef\u7b80\u5199\u4e3aP\uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\uff1a\u8be5\u79cd\u65b9\u5f0f\u9700\u8981\u51c6\u5907 CSV \u6570\u636e\u6587\u4ef6\uff0c\u9002\u5408\u6570\u636e\u91cf\u6bd4\u8f83\u5927\u7684\u60c5\u51b5 \u8c03\u7528 debugtalk.py \u4e2d\u81ea\u5b9a\u4e49\u7684\u51fd\u6570\u751f\u6210\u53c2\u6570\u5217\u8868\uff1a\u8be5\u79cd\u65b9\u5f0f\u6700\u4e3a\u7075\u6d3b\uff0c\u53ef\u901a\u8fc7\u81ea\u5b9a\u4e49 Python \u51fd\u6570\u5b9e\u73b0\u4efb\u610f\u573a\u666f\u7684\u6570\u636e\u9a71\u52a8\u673a\u5236\uff0c\u5f53\u9700\u8981\u52a8\u6001\u751f\u6210\u53c2\u6570\u5217\u8868\u65f6\u4e5f\u9700\u8981\u9009\u62e9\u8be5\u79cd\u65b9\u5f0f \u4e09\u79cd\u65b9\u5f0f\u53ef\u6839\u636e\u5b9e\u9645\u9879\u76ee\u9700\u6c42\u8fdb\u884c\u7075\u6d3b\u9009\u62e9\uff0c\u540c\u65f6\u652f\u6301\u591a\u79cd\u65b9\u5f0f\u7684\u7ec4\u5408\u4f7f\u7528\u3002\u5047\u5982\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b9a\u4e49\u4e86\u591a\u4e2a\u53c2\u6570\uff0c\u90a3\u4e48\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u4f1a\u5bf9\u53c2\u6570\u8fdb\u884c\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\uff0c\u8986\u76d6\u6240\u6709\u53c2\u6570\u7ec4\u5408\u60c5\u51b5\u3002 \u4f7f\u7528\u65b9\u5f0f\u6982\u89c8\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_agent : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ] user_id : ${P(user_id.csv)} username-password : ${get_account(10)} \u53c2\u6570\u914d\u7f6e\u8be6\u89e3 \u00b6 \u5c06\u53c2\u6570\u540d\u79f0\u5b9a\u4e49\u548c\u6570\u636e\u6e90\u6307\u5b9a\u65b9\u5f0f\u8fdb\u884c\u7ec4\u5408\uff0c\u5171\u6709 6 \u79cd\u5f62\u5f0f\u3002\u73b0\u5206\u522b\u9488\u5bf9\u6bcf\u4e00\u7c7b\u60c5\u51b5\u8fdb\u884c\u8be6\u7ec6\u8bf4\u660e\u3002 \u72ec\u7acb\u53c2\u6570 & \u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868 \u00b6 \u5bf9\u4e8e\u53c2\u6570\u5217\u8868\u6bd4\u8f83\u5c0f\u7684\u60c5\u51b5\uff0c\u6700\u7b80\u5355\u7684\u65b9\u5f0f\u662f\u76f4\u63a5\u5728 YAML/JSON \u4e2d\u6307\u5b9a\u53c2\u6570\u5217\u8868\u5185\u5bb9\u3002 \u4f8b\u5982\uff0c\u5bf9\u4e8e\u72ec\u7acb\u53c2\u6570 user_id \uff0c\u53c2\u6570\u5217\u8868\u4e3a [1001, 1002, 1003, 1004] \uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u6309\u7167\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u914d\u7f6e\uff1a config : name : testcase description testcases : create user : testcase : demo-quickstart-6.yml parameters : user_id : [ 1001 , 1002 , 1003 , 1004 ] \u8fdb\u884c\u8be5\u914d\u7f6e\u540e\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u5c31\u4f1a\u5bf9 user_id \u5b9e\u73b0\u6570\u636e\u9a71\u52a8\uff0c\u5373\u5206\u522b\u4f7f\u7528 [1001, 1002, 1003, 1004] \u56db\u4e2a\u503c\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u3002 \u70b9\u51fb\u67e5\u770b\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-quickstart-7.json INFO Start to run testcase: create user 1001 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes . /api/users/1001 INFO POST http://127.0.0.1:5000/api/users/1001 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.021s OK INFO Start to run testcase: create user 1002 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes . /api/users/1002 INFO POST http://127.0.0.1:5000/api/users/1002 INFO status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK INFO Start to run testcase: create user 1003 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes . /api/users/1003 INFO POST http://127.0.0.1:5000/api/users/1003 INFO status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.011s OK INFO Start to run testcase: create user 1004 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes . /api/users/1004 INFO POST http://127.0.0.1:5000/api/users/1004 INFO status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.016s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html \u53ef\u4ee5\u770b\u51fa\uff0c\u6d4b\u8bd5\u7528\u4f8b\u603b\u5171\u8fd0\u884c\u4e86 4 \u6b21\uff0c\u5e76\u4e14\u6bcf\u6b21\u8fd0\u884c\u65f6\u90fd\u662f\u91c7\u7528\u7684\u4e0d\u540c user_id\u3002 \u5173\u8054\u53c2\u6570 & \u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868 \u00b6 \u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u4f8b\u5982 username \u548c password\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u6309\u7167\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u914d\u7f6e\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : - [ \"user1\" , \"111111\" ] - [ \"user2\" , \"222222\" ] - [ \"user3\" , \"333333\" ] \u8fdb\u884c\u8be5\u914d\u7f6e\u540e\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u5c31\u4f1a\u5bf9 username \u548c password \u5b9e\u73b0\u6570\u636e\u9a71\u52a8\uff0c\u5373\u5206\u522b\u4f7f\u7528 {\"username\": \"user1\", \"password\": \"111111\"} \u3001 {\"username\": \"user2\", \"password\": \"222222\"} \u3001 {\"username\": \"user3\", \"password\": \"333333\"} \u8fd0\u884c 3 \u6b21\u6d4b\u8bd5\uff0c\u5e76\u4e14\u4fdd\u8bc1\u53c2\u6570\u503c\u603b\u662f\u6210\u5bf9\u4f7f\u7528\u3002 \u72ec\u7acb\u53c2\u6570 & \u5f15\u7528 CSV \u6587\u4ef6 \u00b6 \u5bf9\u4e8e\u5df2\u6709\u53c2\u6570\u5217\u8868\uff0c\u5e76\u4e14\u6570\u636e\u91cf\u6bd4\u8f83\u5927\u7684\u60c5\u51b5\uff0c\u6bd4\u8f83\u9002\u5408\u7684\u65b9\u5f0f\u662f\u5c06\u53c2\u6570\u5217\u8868\u503c\u5b58\u50a8\u5728 CSV \u6570\u636e\u6587\u4ef6\u4e2d\u3002 \u5bf9\u4e8e CSV \u6570\u636e\u6587\u4ef6\uff0c\u9700\u8981\u9075\u5faa\u5982\u4e0b\u51e0\u9879\u7ea6\u5b9a\u7684\u89c4\u5219\uff1a CSV \u6587\u4ef6\u4e2d\u7684\u7b2c\u4e00\u884c\u5fc5\u987b\u4e3a\u53c2\u6570\u540d\u79f0\uff0c\u4ece\u7b2c\u4e8c\u884c\u5f00\u59cb\u4e3a\u53c2\u6570\u503c\uff0c\u6bcf\u4e2a\uff08\u7ec4\uff09\u503c\u5360\u4e00\u884c\uff1b \u82e5\u540c\u4e00\u4e2a CSV \u6587\u4ef6\u4e2d\u5177\u6709\u591a\u4e2a\u53c2\u6570\uff0c\u5219\u53c2\u6570\u540d\u79f0\u548c\u6570\u503c\u7684\u95f4\u9694\u7b26\u9700\u5b9e\u7528\u82f1\u6587\u9017\u53f7\uff1b \u5728 YAML/JSON \u6587\u4ef6\u5f15\u7528 CSV \u6587\u4ef6\u65f6\uff0c\u6587\u4ef6\u8def\u5f84\u4e3a\u57fa\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\uff08debugtalk.py \u6240\u5728\u8def\u5f84\uff09\u7684\u76f8\u5bf9\u8def\u5f84\u3002 \u4f8b\u5982\uff0cuser_id \u7684\u53c2\u6570\u53d6\u503c\u8303\u56f4\u4e3a 1001\uff5e2000\uff0c\u90a3\u4e48\u6211\u4eec\u5c31\u53ef\u4ee5\u521b\u5efa user_id.csv\uff0c\u5e76\u4e14\u5728\u6587\u4ef6\u4e2d\u6309\u7167\u5982\u4e0b\u5f62\u5f0f\u8fdb\u884c\u63cf\u8ff0\u3002 user_id 1001 1002 ... 1999 2000 \u7136\u540e\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7\u5185\u7f6e\u7684 parameterize \uff08\u53ef\u7b80\u5199\u4e3a P \uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\u3002 \u5047\u8bbe\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e0b\u6709 data \u6587\u4ef6\u5939\uff0cuser_id.csv \u4f4d\u4e8e\u5176\u4e2d\uff0c\u90a3\u4e48 user_id.csv \u7684\u5f15\u7528\u63cf\u8ff0\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_id : ${P(data/user_id.csv)} \u5373 P \u51fd\u6570\u7684\u53c2\u6570\uff08CSV \u6587\u4ef6\u8def\u5f84\uff09\u662f\u76f8\u5bf9\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684\u76f8\u5bf9\u8def\u5f84\u3002\u5f53\u7136\uff0c\u8fd9\u91cc\u4e5f\u53ef\u4ee5\u4f7f\u7528 CSV \u6587\u4ef6\u5728\u7cfb\u7edf\u4e2d\u7684\u7edd\u5bf9\u8def\u5f84\uff0c\u4e0d\u8fc7\u8fd9\u6837\u7684\u8bdd\u5728\u9879\u76ee\u8def\u5f84\u53d8\u52a8\u65f6\u5c31\u4f1a\u51fa\u73b0\u95ee\u9898\uff0c\u56e0\u6b64\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\u7684\u5f62\u5f0f\u3002 \u5173\u8054\u53c2\u6570 & \u5f15\u7528 CSV \u6587\u4ef6 \u00b6 \u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u4f8b\u5982 username \u548c password\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u521b\u5efa account.csv \uff0c\u5e76\u5728\u6587\u4ef6\u4e2d\u6309\u7167\u5982\u4e0b\u5f62\u5f0f\u8fdb\u884c\u63cf\u8ff0\u3002 username,password test1,111111 test2,222222 test3,333333 \u7136\u540e\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7\u5185\u7f6e\u7684 parameterize \uff08\u53ef\u7b80\u5199\u4e3a P \uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\u3002 \u5047\u8bbe\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e0b\u6709 data \u6587\u4ef6\u5939\uff0caccount.csv \u4f4d\u4e8e\u5176\u4e2d\uff0c\u90a3\u4e48 account.csv \u7684\u5f15\u7528\u63cf\u8ff0\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : ${P(data/account.csv)} \u9700\u8981\u8bf4\u660e\u7684\u662f\uff0c\u5728 parameters \u4e2d\u6307\u5b9a\u7684\u53c2\u6570\u540d\u79f0\u5fc5\u987b\u4e0e CSV \u6587\u4ef6\u4e2d\u7b2c\u4e00\u884c\u7684\u53c2\u6570\u540d\u79f0\u4e00\u81f4\uff0c\u987a\u5e8f\u53ef\u4ee5\u4e0d\u4e00\u81f4\uff0c\u53c2\u6570\u4e2a\u6570\u4e5f\u53ef\u4ee5\u4e0d\u4e00\u81f4\u3002 \u4f8b\u5982\uff0c\u5728 account.csv \u6587\u4ef6\u4e2d\u53ef\u4ee5\u5305\u542b\u591a\u4e2a\u53c2\u6570\uff0cusername\u3001password\u3001phone\u3001age\uff1a username,password,phone,age test1,111111,18600000001,21 test2,222222,18600000002,22 test3,333333,18600000003,23 \u800c\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u6307\u5b9a\u53c2\u6570\u65f6\uff0c\u53ef\u4ee5\u53ea\u4f7f\u7528\u90e8\u5206\u53c2\u6570\uff0c\u5e76\u4e14\u53c2\u6570\u987a\u5e8f\u65e0\u9700\u4e0e CSV \u6587\u4ef6\u4e2d\u53c2\u6570\u540d\u79f0\u7684\u987a\u5e8f\u4e00\u81f4\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : phone-username : ${P(account.csv)} \u72ec\u7acb\u53c2\u6570 & \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570 \u00b6 \u5bf9\u4e8e\u6ca1\u6709\u73b0\u6210\u53c2\u6570\u5217\u8868\uff0c\u6216\u8005\u9700\u8981\u66f4\u7075\u6d3b\u7684\u65b9\u5f0f\u52a8\u6001\u751f\u6210\u53c2\u6570\u7684\u60c5\u51b5\uff0c\u53ef\u4ee5\u901a\u8fc7\u5728 debugtalk.py \u4e2d\u81ea\u5b9a\u4e49\u51fd\u6570\u751f\u6210\u53c2\u6570\u5217\u8868\uff0c\u5e76\u5728 YAML/JSON \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u65b9\u5f0f\u3002 \u4f8b\u5982\uff0c\u82e5\u9700\u5bf9 user_id \u8fdb\u884c\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u53c2\u6570\u53d6\u503c\u8303\u56f4\u4e3a 1001\uff5e1004\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728 debugtalk.py \u4e2d\u5b9a\u4e49\u4e00\u4e2a\u51fd\u6570\uff0c\u8fd4\u56de\u53c2\u6570\u5217\u8868\u3002 def get_user_id (): return [ { \"user_id\" : 1001 }, { \"user_id\" : 1002 }, { \"user_id\" : 1003 }, { \"user_id\" : 1004 } ] \u7136\u540e\uff0c\u5728 YAML/JSON \u7684 parameters \u4e2d\u5c31\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u5f62\u5f0f\u6765\u6307\u5b9a\u6570\u636e\u6e90\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_id : ${get_user_id()} \u53e6\u5916\uff0c\u901a\u8fc7\u51fd\u6570\u7684\u4f20\u53c2\u673a\u5236\uff0c\u8fd8\u53ef\u4ee5\u5b9e\u73b0\u66f4\u7075\u6d3b\u7684\u53c2\u6570\u751f\u6210\u529f\u80fd\uff0c\u5728\u8c03\u7528\u51fd\u6570\u65f6\u6307\u5b9a\u9700\u8981\u751f\u6210\u7684\u53c2\u6570\u4e2a\u6570\u3002 \u5173\u8054\u53c2\u6570 & \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570 \u00b6 \u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u5b9e\u73b0\u65b9\u5f0f\u4e5f\u7c7b\u4f3c\u3002 \u4f8b\u5982\uff0c\u5728 debugtalk.py \u4e2d\u5b9a\u4e49\u51fd\u6570 get_account\uff0c\u751f\u6210\u6307\u5b9a\u6570\u91cf\u7684\u8d26\u53f7\u5bc6\u7801\u53c2\u6570\u5217\u8868\u3002 def get_account ( num ): accounts = [] for index in range ( 1 , num + 1 ): accounts . append ( { \"username\" : \"user %s \" % index , \"password\" : str ( index ) * 6 }, ) return accounts \u90a3\u4e48\u5728 YAML/JSON \u7684 parameters \u4e2d\u5c31\u53ef\u4ee5\u8c03\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u751f\u6210\u6307\u5b9a\u6570\u91cf\u7684\u53c2\u6570\u5217\u8868\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : ${get_account(10)} \u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c\u5728\u81ea\u5b9a\u4e49\u51fd\u6570\u4e2d\uff0c\u751f\u6210\u7684\u53c2\u6570\u5217\u8868\u5fc5\u987b\u4e3a list of dict \u7684\u6570\u636e\u7ed3\u6784\uff0c\u8be5\u8bbe\u8ba1\u4e3b\u8981\u662f\u4e3a\u4e86\u4e0e CSV \u6587\u4ef6\u7684\u5904\u7406\u673a\u5236\u4fdd\u6301\u4e00\u81f4\u3002 \u53c2\u6570\u5316\u8fd0\u884c \u00b6 \u5b8c\u6210\u4ee5\u4e0a\u53c2\u6570\u5b9a\u4e49\u548c\u6570\u636e\u6e90\u51c6\u5907\u5de5\u4f5c\u4e4b\u540e\uff0c\u53c2\u6570\u5316\u8fd0\u884c\u4e0e\u666e\u901a\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u5b8c\u5168\u4e00\u81f4\u3002 \u91c7\u7528 hrun \u547d\u4ee4\u8fd0\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\uff1a $ hrun tests/data/demo_parameters.yml \u91c7\u7528 locusts \u547d\u4ee4\u8fd0\u884c\u6027\u80fd\u6d4b\u8bd5\uff1a $ locusts -f tests/data/demo_parameters.yml \u533a\u522b\u5728\u4e8e\uff0c\u81ea\u52a8\u5316\u6d4b\u8bd5\u65f6\u904d\u5386\u4e00\u904d\u540e\u4f1a\u7ec8\u6b62\u6267\u884c\uff0c\u6027\u80fd\u6d4b\u8bd5\u65f6\u6bcf\u4e2a\u5e76\u53d1\u7528\u6237\u90fd\u4f1a\u5faa\u73af\u904d\u5386\u6240\u6709\u53c2\u6570\u3002 \u6848\u4f8b\u6f14\u793a \u00b6 \u5047\u8bbe\u6211\u4eec\u6709\u4e00\u4e2a\u83b7\u53d6 token \u7684 \u6d4b\u8bd5\u7528\u4f8b \u3002 \u70b9\u51fb\u67e5\u770b YAML \u6d4b\u8bd5\u7528\u4f8b - config : name : get token base_url : http://127.0.0.1:5000 variables : device_sn : ${gen_random_string(15)} os_platform : 'ios' app_version : '2.8.6' - test : name : get token with $device_sn, $os_platform, $app_version request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 app_version : $app_version device_sn : $device_sn os_platform : $os_platform json : sign : ${get_sign($device_sn, $os_platform, $app_version)} method : POST url : /api/get-token extract : token : content.token validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] \u5982\u679c\u6211\u4eec\u9700\u8981\u4f7f\u7528 device_sn\u3001app_version \u548c os_platform \u8fd9\u4e09\u4e2a\u53c2\u6570\u6765\u8fdb\u884c\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a testsuite \uff0c\u5e76\u4e14\u8fdb\u884c\u53c2\u6570\u5316\u914d\u7f6e\u3002 config : name : get token with parameters testcases : get token with $user_agent, $app_version, $os_platform : testcase : demo-testcase-get-token.yml parameters : user_agent : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ] app_version : ${P(app_version.csv)} os_platform : ${get_os_platform()} \u5176\u4e2d\uff0c user_agent \u4f7f\u7528\u4e86\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\u7684\u5f62\u5f0f\u3002 app_version \u901a\u8fc7 CSV \u6587\u4ef6\u8fdb\u884c\u53c2\u6570\u914d\u7f6e\uff0c\u5bf9\u5e94\u7684\u6587\u4ef6\u5185\u5bb9\u4e3a\uff1a app_version 2.8.5 2.8.6 os_platform \u4f7f\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u5f62\u5f0f\u751f\u6210\u53c2\u6570\u5217\u8868\uff0c\u5bf9\u5e94\u7684\u51fd\u6570\u5185\u5bb9\u4e3a\uff1a def get_os_platform (): return [ { \"os_platform\" : \"ios\" }, { \"os_platform\" : \"android\" } ] \u90a3\u4e48\uff0c\u7ecf\u8fc7\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\uff0c\u5e94\u8be5\u603b\u5171\u6709 3*2*2=12 \u79cd\u53c2\u6570\u7ec4\u5408\u60c5\u51b5\u3002 \u70b9\u51fb\u67e5\u770b\u5b8c\u6574\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-parameters-get-token.yml INFO Start to run testcase: get token with iOS/10.1, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 10.66 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.026s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 3.03 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.004s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 10.76 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.012s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.49 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.39 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.04 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.005s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 3.44 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.004s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.03 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.005s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.14 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.008s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 7.62 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.010s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.88 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.41 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.008s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1551950193.html","title":"\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8"},{"location":"prepare/parameters/#_1","text":"\u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u4e2d\uff0c\u7ecf\u5e38\u4f1a\u9047\u5230\u5982\u4e0b\u573a\u666f\uff1a \u6d4b\u8bd5\u641c\u7d22\u529f\u80fd\uff0c\u53ea\u6709\u4e00\u4e2a\u641c\u7d22\u8f93\u5165\u6846\uff0c\u4f46\u6709 10 \u79cd\u4e0d\u540c\u7c7b\u578b\u7684\u641c\u7d22\u5173\u952e\u5b57\uff1b \u6d4b\u8bd5\u8d26\u53f7\u767b\u5f55\u529f\u80fd\uff0c\u9700\u8981\u8f93\u5165\u7528\u6237\u540d\u548c\u5bc6\u7801\uff0c\u6309\u7167\u7b49\u4ef7\u7c7b\u5212\u5206\u540e\u6709 20 \u79cd\u7ec4\u5408\u60c5\u51b5\u3002 \u8fd9\u91cc\u53ea\u662f\u968f\u610f\u627e\u4e86\u4e24\u4e2a\u5178\u578b\u7684\u4f8b\u5b50\uff0c\u76f8\u4fe1\u5927\u5bb6\u90fd\u6709\u9047\u5230\u8fc7\u5f88\u591a\u7c7b\u4f3c\u7684\u573a\u666f\u3002\u603b\u7ed3\u4e0b\u6765\uff0c\u5c31\u662f\u5728\u6211\u4eec\u7684\u81ea\u52a8\u5316\u6d4b\u8bd5\u811a\u672c\u4e2d\u5b58\u5728\u53c2\u6570\uff0c\u5e76\u4e14\u6211\u4eec\u9700\u8981\u91c7\u7528\u4e0d\u540c\u7684\u53c2\u6570\u53bb\u8fd0\u884c\u3002 \u7ecf\u8fc7\u6982\u62ec\uff0c\u53c2\u6570\u57fa\u672c\u4e0a\u5206\u4e3a\u4e24\u79cd\u7c7b\u578b\uff1a \u5355\u4e2a\u72ec\u7acb\u53c2\u6570\uff1a\u4f8b\u5982\u524d\u9762\u7684\u7b2c\u4e00\u79cd\u573a\u666f\uff0c\u6211\u4eec\u53ea\u9700\u8981\u53d8\u6362\u641c\u7d22\u5173\u952e\u5b57\u8fd9\u4e00\u4e2a\u53c2\u6570 \u591a\u4e2a\u5177\u6709\u5173\u8054\u6027\u7684\u53c2\u6570\uff1a\u4f8b\u5982\u524d\u9762\u7684\u7b2c\u4e8c\u79cd\u573a\u666f\uff0c\u6211\u4eec\u9700\u8981\u53d8\u6362\u7528\u6237\u540d\u548c\u5bc6\u7801\u4e24\u4e2a\u53c2\u6570\uff0c\u5e76\u4e14\u8fd9\u4e24\u4e2a\u53c2\u6570\u9700\u8981\u5173\u8054\u7ec4\u5408 \u7136\u540e\uff0c\u5bf9\u4e8e\u53c2\u6570\u800c\u8a00\uff0c\u6211\u4eec\u53ef\u80fd\u5177\u6709\u4e00\u4e2a\u53c2\u6570\u5217\u8868\uff0c\u5728\u811a\u672c\u8fd0\u884c\u65f6\u9700\u8981\u6309\u7167\u4e0d\u540c\u7684\u89c4\u5219\u53bb\u53d6\u503c\uff0c\u4f8b\u5982\u987a\u5e8f\u53d6\u503c\u3001\u968f\u673a\u53d6\u503c\u3001\u5faa\u73af\u53d6\u503c\u7b49\u7b49\u3002 \u8fd9\u5c31\u662f\u5178\u578b\u7684\u53c2\u6570\u5316\u548c\u6570\u636e\u9a71\u52a8\u3002 \u5982\u9700\u4e86\u89e3 HttpRunner \u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u673a\u5236\u7684\u5b9e\u73b0\u539f\u7406\u548c\u6280\u672f\u7ec6\u8282\uff0c\u53ef\u524d\u5f80\u9605\u8bfb \u300aHttpRunner \u5b9e\u73b0\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\u673a\u5236\u300b \u3002","title":"\u4ecb\u7ecd"},{"location":"prepare/parameters/#testsuite","text":"\u4ece 2.0.0 \u7248\u672c\u5f00\u59cb\uff0cHttpRunner \u4e0d\u518d\u652f\u6301\u5728\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u8fdb\u884c\u53c2\u6570\u5316\u914d\u7f6e\uff1b\u53c2\u6570\u5316\u7684\u529f\u80fd\u9700\u8981\u5728 testsuite \u4e2d\u5b9e\u73b0\u3002\u53d8\u66f4\u7684\u76ee\u7684\u662f\u8ba9\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u7684\u6982\u5ff5\u66f4\u7eaf\u7cb9\uff0c\u5173\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u548c\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u6982\u5ff5\u5b9a\u4e49\uff0c\u8be6\u89c1 \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u300b \u3002 \u53c2\u6570\u5316\u673a\u5236\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u4e2d\u5b9e\u73b0\u3002\u5982\u9700\u5b9e\u73b0\u6570\u636e\u9a71\u52a8\u673a\u5236\uff0c\u9700\u8981\u521b\u5efa\u4e00\u4e2a testsuite\uff0c\u5728 testsuite \u4e2d\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u5b9a\u4e49\u53c2\u6570\u5316\u914d\u7f6e\u3002 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u7684\u683c\u5f0f\u5982\u4e0b\u6240\u793a\uff1a config : name : testsuite description testcases : testcase1_name : testcase : /path/to/testcase1 testcase2_name : testcase : /path/to/testcase2 \u9700\u8981\u6ce8\u610f\u7684\u662f\uff0ctestsuite \u548c testcase \u7684\u683c\u5f0f\u5b58\u5728\u8f83\u5927\u533a\u522b\uff0c\u8be6\u89c1 \u300a\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u300b \u3002","title":"\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u51c6\u5907"},{"location":"prepare/parameters/#_2","text":"\u5982\u9700\u5bf9\u67d0\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5b9e\u73b0\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u9700\u8981\u4f7f\u7528 parameters \u5173\u952e\u5b57\uff0c\u5b9a\u4e49\u53c2\u6570\u540d\u79f0\u5e76\u6307\u5b9a\u6570\u636e\u6e90\u53d6\u503c\u65b9\u5f0f\u3002 \u53c2\u6570\u540d\u79f0\u7684\u5b9a\u4e49\u5206\u4e3a\u4e24\u79cd\u60c5\u51b5\uff1a \u72ec\u7acb\u53c2\u6570\u5355\u72ec\u8fdb\u884c\u5b9a\u4e49\uff1b \u591a\u4e2a\u53c2\u6570\u5177\u6709\u5173\u8054\u6027\u7684\u53c2\u6570\u9700\u8981\u5c06\u5176\u5b9a\u4e49\u5728\u4e00\u8d77\uff0c\u91c7\u7528\u77ed\u6a2a\u7ebf\uff08 - \uff09\u8fdb\u884c\u8fde\u63a5\u3002 \u6570\u636e\u6e90\u6307\u5b9a\u652f\u6301\u4e09\u79cd\u65b9\u5f0f\uff1a \u5728 YAML/JSON \u4e2d\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\uff1a\u8be5\u79cd\u65b9\u5f0f\u6700\u4e3a\u7b80\u5355\u6613\u7528\uff0c\u9002\u5408\u53c2\u6570\u5217\u8868\u6bd4\u8f83\u5c0f\u7684\u60c5\u51b5 \u901a\u8fc7\u5185\u7f6e\u7684 parameterize\uff08\u53ef\u7b80\u5199\u4e3aP\uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\uff1a\u8be5\u79cd\u65b9\u5f0f\u9700\u8981\u51c6\u5907 CSV \u6570\u636e\u6587\u4ef6\uff0c\u9002\u5408\u6570\u636e\u91cf\u6bd4\u8f83\u5927\u7684\u60c5\u51b5 \u8c03\u7528 debugtalk.py \u4e2d\u81ea\u5b9a\u4e49\u7684\u51fd\u6570\u751f\u6210\u53c2\u6570\u5217\u8868\uff1a\u8be5\u79cd\u65b9\u5f0f\u6700\u4e3a\u7075\u6d3b\uff0c\u53ef\u901a\u8fc7\u81ea\u5b9a\u4e49 Python \u51fd\u6570\u5b9e\u73b0\u4efb\u610f\u573a\u666f\u7684\u6570\u636e\u9a71\u52a8\u673a\u5236\uff0c\u5f53\u9700\u8981\u52a8\u6001\u751f\u6210\u53c2\u6570\u5217\u8868\u65f6\u4e5f\u9700\u8981\u9009\u62e9\u8be5\u79cd\u65b9\u5f0f \u4e09\u79cd\u65b9\u5f0f\u53ef\u6839\u636e\u5b9e\u9645\u9879\u76ee\u9700\u6c42\u8fdb\u884c\u7075\u6d3b\u9009\u62e9\uff0c\u540c\u65f6\u652f\u6301\u591a\u79cd\u65b9\u5f0f\u7684\u7ec4\u5408\u4f7f\u7528\u3002\u5047\u5982\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b9a\u4e49\u4e86\u591a\u4e2a\u53c2\u6570\uff0c\u90a3\u4e48\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u4f1a\u5bf9\u53c2\u6570\u8fdb\u884c\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\uff0c\u8986\u76d6\u6240\u6709\u53c2\u6570\u7ec4\u5408\u60c5\u51b5\u3002 \u4f7f\u7528\u65b9\u5f0f\u6982\u89c8\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_agent : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ] user_id : ${P(user_id.csv)} username-password : ${get_account(10)}","title":"\u53c2\u6570\u914d\u7f6e\u6982\u8ff0"},{"location":"prepare/parameters/#_3","text":"\u5c06\u53c2\u6570\u540d\u79f0\u5b9a\u4e49\u548c\u6570\u636e\u6e90\u6307\u5b9a\u65b9\u5f0f\u8fdb\u884c\u7ec4\u5408\uff0c\u5171\u6709 6 \u79cd\u5f62\u5f0f\u3002\u73b0\u5206\u522b\u9488\u5bf9\u6bcf\u4e00\u7c7b\u60c5\u51b5\u8fdb\u884c\u8be6\u7ec6\u8bf4\u660e\u3002","title":"\u53c2\u6570\u914d\u7f6e\u8be6\u89e3"},{"location":"prepare/parameters/#_4","text":"\u5bf9\u4e8e\u53c2\u6570\u5217\u8868\u6bd4\u8f83\u5c0f\u7684\u60c5\u51b5\uff0c\u6700\u7b80\u5355\u7684\u65b9\u5f0f\u662f\u76f4\u63a5\u5728 YAML/JSON \u4e2d\u6307\u5b9a\u53c2\u6570\u5217\u8868\u5185\u5bb9\u3002 \u4f8b\u5982\uff0c\u5bf9\u4e8e\u72ec\u7acb\u53c2\u6570 user_id \uff0c\u53c2\u6570\u5217\u8868\u4e3a [1001, 1002, 1003, 1004] \uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u6309\u7167\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u914d\u7f6e\uff1a config : name : testcase description testcases : create user : testcase : demo-quickstart-6.yml parameters : user_id : [ 1001 , 1002 , 1003 , 1004 ] \u8fdb\u884c\u8be5\u914d\u7f6e\u540e\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u5c31\u4f1a\u5bf9 user_id \u5b9e\u73b0\u6570\u636e\u9a71\u52a8\uff0c\u5373\u5206\u522b\u4f7f\u7528 [1001, 1002, 1003, 1004] \u56db\u4e2a\u503c\u8fd0\u884c\u6d4b\u8bd5\u7528\u4f8b\u3002 \u70b9\u51fb\u67e5\u770b\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-quickstart-7.json INFO Start to run testcase: create user 1001 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 8.95 ms, response_length: 46 bytes . /api/users/1001 INFO POST http://127.0.0.1:5000/api/users/1001 INFO status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.021s OK INFO Start to run testcase: create user 1002 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.78 ms, response_length: 46 bytes . /api/users/1002 INFO POST http://127.0.0.1:5000/api/users/1002 INFO status_code: 201, response_time(ms): 2.84 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK INFO Start to run testcase: create user 1003 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 2.92 ms, response_length: 46 bytes . /api/users/1003 INFO POST http://127.0.0.1:5000/api/users/1003 INFO status_code: 201, response_time(ms): 5.56 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.011s OK INFO Start to run testcase: create user 1004 /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.25 ms, response_length: 46 bytes . /api/users/1004 INFO POST http://127.0.0.1:5000/api/users/1004 INFO status_code: 201, response_time(ms): 7.02 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.016s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548518757.html \u53ef\u4ee5\u770b\u51fa\uff0c\u6d4b\u8bd5\u7528\u4f8b\u603b\u5171\u8fd0\u884c\u4e86 4 \u6b21\uff0c\u5e76\u4e14\u6bcf\u6b21\u8fd0\u884c\u65f6\u90fd\u662f\u91c7\u7528\u7684\u4e0d\u540c user_id\u3002","title":"\u72ec\u7acb\u53c2\u6570 & \u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868"},{"location":"prepare/parameters/#_5","text":"\u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u4f8b\u5982 username \u548c password\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u6309\u7167\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u914d\u7f6e\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : - [ \"user1\" , \"111111\" ] - [ \"user2\" , \"222222\" ] - [ \"user3\" , \"333333\" ] \u8fdb\u884c\u8be5\u914d\u7f6e\u540e\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u65f6\u5c31\u4f1a\u5bf9 username \u548c password \u5b9e\u73b0\u6570\u636e\u9a71\u52a8\uff0c\u5373\u5206\u522b\u4f7f\u7528 {\"username\": \"user1\", \"password\": \"111111\"} \u3001 {\"username\": \"user2\", \"password\": \"222222\"} \u3001 {\"username\": \"user3\", \"password\": \"333333\"} \u8fd0\u884c 3 \u6b21\u6d4b\u8bd5\uff0c\u5e76\u4e14\u4fdd\u8bc1\u53c2\u6570\u503c\u603b\u662f\u6210\u5bf9\u4f7f\u7528\u3002","title":"\u5173\u8054\u53c2\u6570 & \u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868"},{"location":"prepare/parameters/#csv","text":"\u5bf9\u4e8e\u5df2\u6709\u53c2\u6570\u5217\u8868\uff0c\u5e76\u4e14\u6570\u636e\u91cf\u6bd4\u8f83\u5927\u7684\u60c5\u51b5\uff0c\u6bd4\u8f83\u9002\u5408\u7684\u65b9\u5f0f\u662f\u5c06\u53c2\u6570\u5217\u8868\u503c\u5b58\u50a8\u5728 CSV \u6570\u636e\u6587\u4ef6\u4e2d\u3002 \u5bf9\u4e8e CSV \u6570\u636e\u6587\u4ef6\uff0c\u9700\u8981\u9075\u5faa\u5982\u4e0b\u51e0\u9879\u7ea6\u5b9a\u7684\u89c4\u5219\uff1a CSV \u6587\u4ef6\u4e2d\u7684\u7b2c\u4e00\u884c\u5fc5\u987b\u4e3a\u53c2\u6570\u540d\u79f0\uff0c\u4ece\u7b2c\u4e8c\u884c\u5f00\u59cb\u4e3a\u53c2\u6570\u503c\uff0c\u6bcf\u4e2a\uff08\u7ec4\uff09\u503c\u5360\u4e00\u884c\uff1b \u82e5\u540c\u4e00\u4e2a CSV \u6587\u4ef6\u4e2d\u5177\u6709\u591a\u4e2a\u53c2\u6570\uff0c\u5219\u53c2\u6570\u540d\u79f0\u548c\u6570\u503c\u7684\u95f4\u9694\u7b26\u9700\u5b9e\u7528\u82f1\u6587\u9017\u53f7\uff1b \u5728 YAML/JSON \u6587\u4ef6\u5f15\u7528 CSV \u6587\u4ef6\u65f6\uff0c\u6587\u4ef6\u8def\u5f84\u4e3a\u57fa\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\uff08debugtalk.py \u6240\u5728\u8def\u5f84\uff09\u7684\u76f8\u5bf9\u8def\u5f84\u3002 \u4f8b\u5982\uff0cuser_id \u7684\u53c2\u6570\u53d6\u503c\u8303\u56f4\u4e3a 1001\uff5e2000\uff0c\u90a3\u4e48\u6211\u4eec\u5c31\u53ef\u4ee5\u521b\u5efa user_id.csv\uff0c\u5e76\u4e14\u5728\u6587\u4ef6\u4e2d\u6309\u7167\u5982\u4e0b\u5f62\u5f0f\u8fdb\u884c\u63cf\u8ff0\u3002 user_id 1001 1002 ... 1999 2000 \u7136\u540e\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7\u5185\u7f6e\u7684 parameterize \uff08\u53ef\u7b80\u5199\u4e3a P \uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\u3002 \u5047\u8bbe\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e0b\u6709 data \u6587\u4ef6\u5939\uff0cuser_id.csv \u4f4d\u4e8e\u5176\u4e2d\uff0c\u90a3\u4e48 user_id.csv \u7684\u5f15\u7528\u63cf\u8ff0\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_id : ${P(data/user_id.csv)} \u5373 P \u51fd\u6570\u7684\u53c2\u6570\uff08CSV \u6587\u4ef6\u8def\u5f84\uff09\u662f\u76f8\u5bf9\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684\u76f8\u5bf9\u8def\u5f84\u3002\u5f53\u7136\uff0c\u8fd9\u91cc\u4e5f\u53ef\u4ee5\u4f7f\u7528 CSV \u6587\u4ef6\u5728\u7cfb\u7edf\u4e2d\u7684\u7edd\u5bf9\u8def\u5f84\uff0c\u4e0d\u8fc7\u8fd9\u6837\u7684\u8bdd\u5728\u9879\u76ee\u8def\u5f84\u53d8\u52a8\u65f6\u5c31\u4f1a\u51fa\u73b0\u95ee\u9898\uff0c\u56e0\u6b64\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\u7684\u5f62\u5f0f\u3002","title":"\u72ec\u7acb\u53c2\u6570 & \u5f15\u7528 CSV \u6587\u4ef6"},{"location":"prepare/parameters/#csv_1","text":"\u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u4f8b\u5982 username \u548c password\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u521b\u5efa account.csv \uff0c\u5e76\u5728\u6587\u4ef6\u4e2d\u6309\u7167\u5982\u4e0b\u5f62\u5f0f\u8fdb\u884c\u63cf\u8ff0\u3002 username,password test1,111111 test2,222222 test3,333333 \u7136\u540e\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\uff0c\u5c31\u53ef\u4ee5\u901a\u8fc7\u5185\u7f6e\u7684 parameterize \uff08\u53ef\u7b80\u5199\u4e3a P \uff09\u51fd\u6570\u5f15\u7528 CSV \u6587\u4ef6\u3002 \u5047\u8bbe\u9879\u76ee\u7684\u6839\u76ee\u5f55\u4e0b\u6709 data \u6587\u4ef6\u5939\uff0caccount.csv \u4f4d\u4e8e\u5176\u4e2d\uff0c\u90a3\u4e48 account.csv \u7684\u5f15\u7528\u63cf\u8ff0\u5982\u4e0b\uff1a config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : ${P(data/account.csv)} \u9700\u8981\u8bf4\u660e\u7684\u662f\uff0c\u5728 parameters \u4e2d\u6307\u5b9a\u7684\u53c2\u6570\u540d\u79f0\u5fc5\u987b\u4e0e CSV \u6587\u4ef6\u4e2d\u7b2c\u4e00\u884c\u7684\u53c2\u6570\u540d\u79f0\u4e00\u81f4\uff0c\u987a\u5e8f\u53ef\u4ee5\u4e0d\u4e00\u81f4\uff0c\u53c2\u6570\u4e2a\u6570\u4e5f\u53ef\u4ee5\u4e0d\u4e00\u81f4\u3002 \u4f8b\u5982\uff0c\u5728 account.csv \u6587\u4ef6\u4e2d\u53ef\u4ee5\u5305\u542b\u591a\u4e2a\u53c2\u6570\uff0cusername\u3001password\u3001phone\u3001age\uff1a username,password,phone,age test1,111111,18600000001,21 test2,222222,18600000002,22 test3,333333,18600000003,23 \u800c\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u6307\u5b9a\u53c2\u6570\u65f6\uff0c\u53ef\u4ee5\u53ea\u4f7f\u7528\u90e8\u5206\u53c2\u6570\uff0c\u5e76\u4e14\u53c2\u6570\u987a\u5e8f\u65e0\u9700\u4e0e CSV \u6587\u4ef6\u4e2d\u53c2\u6570\u540d\u79f0\u7684\u987a\u5e8f\u4e00\u81f4\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : phone-username : ${P(account.csv)}","title":"\u5173\u8054\u53c2\u6570 & \u5f15\u7528 CSV \u6587\u4ef6"},{"location":"prepare/parameters/#_6","text":"\u5bf9\u4e8e\u6ca1\u6709\u73b0\u6210\u53c2\u6570\u5217\u8868\uff0c\u6216\u8005\u9700\u8981\u66f4\u7075\u6d3b\u7684\u65b9\u5f0f\u52a8\u6001\u751f\u6210\u53c2\u6570\u7684\u60c5\u51b5\uff0c\u53ef\u4ee5\u901a\u8fc7\u5728 debugtalk.py \u4e2d\u81ea\u5b9a\u4e49\u51fd\u6570\u751f\u6210\u53c2\u6570\u5217\u8868\uff0c\u5e76\u5728 YAML/JSON \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u65b9\u5f0f\u3002 \u4f8b\u5982\uff0c\u82e5\u9700\u5bf9 user_id \u8fdb\u884c\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u53c2\u6570\u53d6\u503c\u8303\u56f4\u4e3a 1001\uff5e1004\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728 debugtalk.py \u4e2d\u5b9a\u4e49\u4e00\u4e2a\u51fd\u6570\uff0c\u8fd4\u56de\u53c2\u6570\u5217\u8868\u3002 def get_user_id (): return [ { \"user_id\" : 1001 }, { \"user_id\" : 1002 }, { \"user_id\" : 1003 }, { \"user_id\" : 1004 } ] \u7136\u540e\uff0c\u5728 YAML/JSON \u7684 parameters \u4e2d\u5c31\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u5f62\u5f0f\u6765\u6307\u5b9a\u6570\u636e\u6e90\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : user_id : ${get_user_id()} \u53e6\u5916\uff0c\u901a\u8fc7\u51fd\u6570\u7684\u4f20\u53c2\u673a\u5236\uff0c\u8fd8\u53ef\u4ee5\u5b9e\u73b0\u66f4\u7075\u6d3b\u7684\u53c2\u6570\u751f\u6210\u529f\u80fd\uff0c\u5728\u8c03\u7528\u51fd\u6570\u65f6\u6307\u5b9a\u9700\u8981\u751f\u6210\u7684\u53c2\u6570\u4e2a\u6570\u3002","title":"\u72ec\u7acb\u53c2\u6570 & \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570"},{"location":"prepare/parameters/#_7","text":"\u5bf9\u4e8e\u5177\u6709\u5173\u8054\u6027\u7684\u591a\u4e2a\u53c2\u6570\uff0c\u5b9e\u73b0\u65b9\u5f0f\u4e5f\u7c7b\u4f3c\u3002 \u4f8b\u5982\uff0c\u5728 debugtalk.py \u4e2d\u5b9a\u4e49\u51fd\u6570 get_account\uff0c\u751f\u6210\u6307\u5b9a\u6570\u91cf\u7684\u8d26\u53f7\u5bc6\u7801\u53c2\u6570\u5217\u8868\u3002 def get_account ( num ): accounts = [] for index in range ( 1 , num + 1 ): accounts . append ( { \"username\" : \"user %s \" % index , \"password\" : str ( index ) * 6 }, ) return accounts \u90a3\u4e48\u5728 YAML/JSON \u7684 parameters \u4e2d\u5c31\u53ef\u4ee5\u8c03\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u751f\u6210\u6307\u5b9a\u6570\u91cf\u7684\u53c2\u6570\u5217\u8868\u3002 config : name : \"demo\" testcases : testcase1_name : testcase : /path/to/testcase1 parameters : username-password : ${get_account(10)} \u9700\u8981\u6ce8\u610f\u7684\u662f\uff0c\u5728\u81ea\u5b9a\u4e49\u51fd\u6570\u4e2d\uff0c\u751f\u6210\u7684\u53c2\u6570\u5217\u8868\u5fc5\u987b\u4e3a list of dict \u7684\u6570\u636e\u7ed3\u6784\uff0c\u8be5\u8bbe\u8ba1\u4e3b\u8981\u662f\u4e3a\u4e86\u4e0e CSV \u6587\u4ef6\u7684\u5904\u7406\u673a\u5236\u4fdd\u6301\u4e00\u81f4\u3002","title":"\u5173\u8054\u53c2\u6570 & \u5f15\u7528\u81ea\u5b9a\u4e49\u51fd\u6570"},{"location":"prepare/parameters/#_8","text":"\u5b8c\u6210\u4ee5\u4e0a\u53c2\u6570\u5b9a\u4e49\u548c\u6570\u636e\u6e90\u51c6\u5907\u5de5\u4f5c\u4e4b\u540e\uff0c\u53c2\u6570\u5316\u8fd0\u884c\u4e0e\u666e\u901a\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u5b8c\u5168\u4e00\u81f4\u3002 \u91c7\u7528 hrun \u547d\u4ee4\u8fd0\u884c\u81ea\u52a8\u5316\u6d4b\u8bd5\uff1a $ hrun tests/data/demo_parameters.yml \u91c7\u7528 locusts \u547d\u4ee4\u8fd0\u884c\u6027\u80fd\u6d4b\u8bd5\uff1a $ locusts -f tests/data/demo_parameters.yml \u533a\u522b\u5728\u4e8e\uff0c\u81ea\u52a8\u5316\u6d4b\u8bd5\u65f6\u904d\u5386\u4e00\u904d\u540e\u4f1a\u7ec8\u6b62\u6267\u884c\uff0c\u6027\u80fd\u6d4b\u8bd5\u65f6\u6bcf\u4e2a\u5e76\u53d1\u7528\u6237\u90fd\u4f1a\u5faa\u73af\u904d\u5386\u6240\u6709\u53c2\u6570\u3002","title":"\u53c2\u6570\u5316\u8fd0\u884c"},{"location":"prepare/parameters/#_9","text":"\u5047\u8bbe\u6211\u4eec\u6709\u4e00\u4e2a\u83b7\u53d6 token \u7684 \u6d4b\u8bd5\u7528\u4f8b \u3002 \u70b9\u51fb\u67e5\u770b YAML \u6d4b\u8bd5\u7528\u4f8b - config : name : get token base_url : http://127.0.0.1:5000 variables : device_sn : ${gen_random_string(15)} os_platform : 'ios' app_version : '2.8.6' - test : name : get token with $device_sn, $os_platform, $app_version request : headers : Content-Type : application/json User-Agent : python-requests/2.18.4 app_version : $app_version device_sn : $device_sn os_platform : $os_platform json : sign : ${get_sign($device_sn, $os_platform, $app_version)} method : POST url : /api/get-token extract : token : content.token validate : - eq : [ status_code , 200 ] - eq : [ headers.Content-Type , application/json ] - eq : [ content.success , true ] \u5982\u679c\u6211\u4eec\u9700\u8981\u4f7f\u7528 device_sn\u3001app_version \u548c os_platform \u8fd9\u4e09\u4e2a\u53c2\u6570\u6765\u8fdb\u884c\u53c2\u6570\u5316\u6570\u636e\u9a71\u52a8\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a testsuite \uff0c\u5e76\u4e14\u8fdb\u884c\u53c2\u6570\u5316\u914d\u7f6e\u3002 config : name : get token with parameters testcases : get token with $user_agent, $app_version, $os_platform : testcase : demo-testcase-get-token.yml parameters : user_agent : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ] app_version : ${P(app_version.csv)} os_platform : ${get_os_platform()} \u5176\u4e2d\uff0c user_agent \u4f7f\u7528\u4e86\u76f4\u63a5\u6307\u5b9a\u53c2\u6570\u5217\u8868\u7684\u5f62\u5f0f\u3002 app_version \u901a\u8fc7 CSV \u6587\u4ef6\u8fdb\u884c\u53c2\u6570\u914d\u7f6e\uff0c\u5bf9\u5e94\u7684\u6587\u4ef6\u5185\u5bb9\u4e3a\uff1a app_version 2.8.5 2.8.6 os_platform \u4f7f\u7528\u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u5f62\u5f0f\u751f\u6210\u53c2\u6570\u5217\u8868\uff0c\u5bf9\u5e94\u7684\u51fd\u6570\u5185\u5bb9\u4e3a\uff1a def get_os_platform (): return [ { \"os_platform\" : \"ios\" }, { \"os_platform\" : \"android\" } ] \u90a3\u4e48\uff0c\u7ecf\u8fc7\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\uff0c\u5e94\u8be5\u603b\u5171\u6709 3*2*2=12 \u79cd\u53c2\u6570\u7ec4\u5408\u60c5\u51b5\u3002 \u70b9\u51fb\u67e5\u770b\u5b8c\u6574\u8fd0\u884c\u65e5\u5fd7 $ hrun docs/data/demo-parameters-get-token.yml INFO Start to run testcase: get token with iOS/10.1, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 10.66 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.026s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 3.03 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.004s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 10.76 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.012s OK INFO Start to run testcase: get token with iOS/10.1, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.49 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.39 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.04 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.005s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 3.44 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.004s OK INFO Start to run testcase: get token with iOS/10.2, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.03 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.005s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.5, ios get token with PBJda7SXM2ReWlu, ios, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.14 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.008s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.5, android get token with PBJda7SXM2ReWlu, android, 2.8.5 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 7.62 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.010s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.6, ios get token with PBJda7SXM2ReWlu, ios, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 4.88 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.006s OK INFO Start to run testcase: get token with iOS/10.3, 2.8.6, android get token with PBJda7SXM2ReWlu, android, 2.8.6 INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 5.41 ms, response_length: 46 bytes . ---------------------------------------------------------------------- Ran 1 test in 0.008s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1551950193.html","title":"\u6848\u4f8b\u6f14\u793a"},{"location":"prepare/project-structure/","text":"\u6587\u4ef6\u7c7b\u578b\b\u8bf4\u660e \u00b6 \u5728 HttpRunner \u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\u4e2d\uff0c\u4e3b\u8981\u5b58\u5728\u5982\u4e0b\u51e0\u7c7b\u6587\u4ef6\uff1a YAML/JSON \uff08\u5fc5\u987b\uff09\uff1a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\uff0c\u5b58\u50a8\u63a5\u53e3\u6d4b\u8bd5\u76f8\u5173\u4fe1\u606f debugtalk.py \uff08\u53ef\u9009\uff09\uff1a\u5b58\u50a8\u9879\u76ee\u4e2d\u903b\u8f91\u8fd0\u7b97\u8f85\u52a9\u51fd\u6570 \u8be5\u6587\u4ef6\u5b58\u5728\u65f6\uff0c\u5c06\u4f5c\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\u5b9a\u4f4d\u6807\u8bb0\uff0c\u5176\u6240\u5728\u76ee\u5f55\u5373\u88ab\u89c6\u4e3a\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u8be5\u6587\u4ef6\u4e0d\u5b58\u5728\u65f6\uff0c\u8fd0\u884c\u6d4b\u8bd5\u7684\u6240\u5728\u8def\u5f84\uff08 CWD \uff09\u5c06\u88ab\u89c6\u4e3a\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u7684\u76f8\u5bf9\u8def\u5f84\uff08\u4f8b\u5982 .csv \uff09\u5747\u9700\u57fa\u4e8e\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u8fd0\u884c\u6d4b\u8bd5\u540e\uff0c\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u5939\uff08 reports \uff09\u4f1a\u751f\u6210\u5728\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 .env \uff08\u53ef\u9009\uff09\uff1a\u5b58\u50a8\u9879\u76ee\u73af\u5883\u53d8\u91cf\uff0c\u901a\u5e38\u7528\u4e8e\u5b58\u50a8\u9879\u76ee\u654f\u611f\u4fe1\u606f .csv \uff08\u53ef\u9009\uff09\uff1a\u9879\u76ee\u6570\u636e\u6587\u4ef6\uff0c\u7528\u4e8e\u8fdb\u884c\u6570\u636e\u9a71\u52a8 reports \uff1a\u9ed8\u8ba4\u751f\u6210\u6d4b\u8bd5\u62a5\u544a\u7684\u5b58\u50a8\u6587\u4ef6\u5939 \u9879\u76ee\u6587\u4ef6\u7ed3\u6784 \u00b6 \u5bf9\u4e8e\u63a5\u53e3\u6570\u6bd4\u8f83\u5c11\uff0c\u6216\u8005\u6d4b\u8bd5\u573a\u666f\u6bd4\u8f83\u7b80\u5355\u7684\u9879\u76ee\uff0c\u7ec4\u7ec7\u6d4b\u8bd5\u7528\u4f8b\u65f6\u65e0\u9700\u5206\u5c42\u3002\u5728\u6b64\u79cd\u60c5\u51b5\u4e0b\uff0c\u9879\u76ee\u6587\u4ef6\u7684\u76ee\u5f55\u7ed3\u6784\u6ca1\u6709\u4efb\u4f55\u8981\u6c42\uff0c\u5728\u9879\u76ee\u4e2d\u53ea\u9700\u8981\b\u4e00\u5806 YAML/JSON \u6587\u4ef6\u5373\u53ef\uff0c\u6bcf\u4e00\u4e2a\u6587\u4ef6\u5355\u72ec\u5bf9\u5e94\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\uff1b\u6839\u636e\u9700\u8981\uff0c\u9879\u76ee\u4e2d\u53ef\u80fd\u8fd8\u4f1a\u6709 debugtalk.py \u3001 .env \u7b49\u6587\u4ef6\u3002 \u63a8\u8350\u7684\u9879\u76ee\u6587\u4ef6\u76ee\u5f55\u7ed3\u6784\u793a\u4f8b\u5982\u4e0b\uff1a $ tree demo -a demo \u251c\u2500\u2500 .env \u251c\u2500\u2500 debugtalk.py \u251c\u2500\u2500 reports \u251c\u2500\u2500 testcase1.yml \u2514\u2500\u2500 testcase2.json","title":"\u9879\u76ee\u6587\u4ef6\u7ec4\u7ec7"},{"location":"prepare/project-structure/#_1","text":"\u5728 HttpRunner \u81ea\u52a8\u5316\u6d4b\u8bd5\u9879\u76ee\u4e2d\uff0c\u4e3b\u8981\u5b58\u5728\u5982\u4e0b\u51e0\u7c7b\u6587\u4ef6\uff1a YAML/JSON \uff08\u5fc5\u987b\uff09\uff1a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\uff0c\u5b58\u50a8\u63a5\u53e3\u6d4b\u8bd5\u76f8\u5173\u4fe1\u606f debugtalk.py \uff08\u53ef\u9009\uff09\uff1a\u5b58\u50a8\u9879\u76ee\u4e2d\u903b\u8f91\u8fd0\u7b97\u8f85\u52a9\u51fd\u6570 \u8be5\u6587\u4ef6\u5b58\u5728\u65f6\uff0c\u5c06\u4f5c\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\u5b9a\u4f4d\u6807\u8bb0\uff0c\u5176\u6240\u5728\u76ee\u5f55\u5373\u88ab\u89c6\u4e3a\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u8be5\u6587\u4ef6\u4e0d\u5b58\u5728\u65f6\uff0c\u8fd0\u884c\u6d4b\u8bd5\u7684\u6240\u5728\u8def\u5f84\uff08 CWD \uff09\u5c06\u88ab\u89c6\u4e3a\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u4e2d\u7684\u76f8\u5bf9\u8def\u5f84\uff08\u4f8b\u5982 .csv \uff09\u5747\u9700\u57fa\u4e8e\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 \u8fd0\u884c\u6d4b\u8bd5\u540e\uff0c\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u5939\uff08 reports \uff09\u4f1a\u751f\u6210\u5728\u9879\u76ee\u5de5\u7a0b\u6839\u76ee\u5f55 .env \uff08\u53ef\u9009\uff09\uff1a\u5b58\u50a8\u9879\u76ee\u73af\u5883\u53d8\u91cf\uff0c\u901a\u5e38\u7528\u4e8e\u5b58\u50a8\u9879\u76ee\u654f\u611f\u4fe1\u606f .csv \uff08\u53ef\u9009\uff09\uff1a\u9879\u76ee\u6570\u636e\u6587\u4ef6\uff0c\u7528\u4e8e\u8fdb\u884c\u6570\u636e\u9a71\u52a8 reports \uff1a\u9ed8\u8ba4\u751f\u6210\u6d4b\u8bd5\u62a5\u544a\u7684\u5b58\u50a8\u6587\u4ef6\u5939","title":"\u6587\u4ef6\u7c7b\u578b\b\u8bf4\u660e"},{"location":"prepare/project-structure/#_2","text":"\u5bf9\u4e8e\u63a5\u53e3\u6570\u6bd4\u8f83\u5c11\uff0c\u6216\u8005\u6d4b\u8bd5\u573a\u666f\u6bd4\u8f83\u7b80\u5355\u7684\u9879\u76ee\uff0c\u7ec4\u7ec7\u6d4b\u8bd5\u7528\u4f8b\u65f6\u65e0\u9700\u5206\u5c42\u3002\u5728\u6b64\u79cd\u60c5\u51b5\u4e0b\uff0c\u9879\u76ee\u6587\u4ef6\u7684\u76ee\u5f55\u7ed3\u6784\u6ca1\u6709\u4efb\u4f55\u8981\u6c42\uff0c\u5728\u9879\u76ee\u4e2d\u53ea\u9700\u8981\b\u4e00\u5806 YAML/JSON \u6587\u4ef6\u5373\u53ef\uff0c\u6bcf\u4e00\u4e2a\u6587\u4ef6\u5355\u72ec\u5bf9\u5e94\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\uff1b\u6839\u636e\u9700\u8981\uff0c\u9879\u76ee\u4e2d\u53ef\u80fd\u8fd8\u4f1a\u6709 debugtalk.py \u3001 .env \u7b49\u6587\u4ef6\u3002 \u63a8\u8350\u7684\u9879\u76ee\u6587\u4ef6\u76ee\u5f55\u7ed3\u6784\u793a\u4f8b\u5982\u4e0b\uff1a $ tree demo -a demo \u251c\u2500\u2500 .env \u251c\u2500\u2500 debugtalk.py \u251c\u2500\u2500 reports \u251c\u2500\u2500 testcase1.yml \u2514\u2500\u2500 testcase2.json","title":"\u9879\u76ee\u6587\u4ef6\u7ed3\u6784"},{"location":"prepare/record/","text":"\u4e3a\u4e86\u7b80\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u7f16\u5199\u5de5\u4f5c\uff0cHttpRunner \u5b9e\u73b0\u4e86\u6d4b\u8bd5\u7528\u4f8b\u751f\u6210\u7684\u529f\u80fd\uff0c\u5bf9\u5e94\u7684\u8f6c\u6362\u5de5\u5177\u4e3a\u4e00\u4e2a\u72ec\u7acb\u7684\u9879\u76ee\uff1a har2case \u3002 \u7b80\u5355\u6765\u8bf4\uff0c\u5c31\u662f\u5f53\u524d\u4e3b\u6d41\u7684\u6293\u5305\u5de5\u5177\u548c\u6d4f\u89c8\u5668\u90fd\u652f\u6301\u5c06\u6293\u53d6\u5f97\u5230\u7684\u6570\u636e\u5305\u5bfc\u51fa\u4e3a\u6807\u51c6\u901a\u7528\u7684 HAR \u683c\u5f0f\uff08HTTP Archive\uff09\uff0c\u7136\u540e HttpRunner \u5b9e\u73b0\u4e86\u5c06 HAR \u683c\u5f0f\u7684\u6570\u636e\u5305\u8f6c\u6362\u4e3a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u529f\u80fd\u3002 \u83b7\u53d6 HAR \u6570\u636e\u5305 \u00b6 \u5728\u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u4e4b\u524d\uff0c\u9700\u8981\u5148\u5c06\u6293\u53d6\u5f97\u5230\u7684\u6570\u636e\u5305\u5bfc\u51fa\u4e3a HAR \u683c\u5f0f\u7684\u6587\u4ef6\u3002\u5728 Charles Proxy \u4e2d\u7684\u64cd\u4f5c\u65b9\u5f0f\u4e3a\uff0c\u9009\u4e2d\u9700\u8981\u8f6c\u6362\u7684\u63a5\u53e3\uff08\u53ef\u591a\u9009\u6216\u5168\u9009\uff09\uff0c\u70b9\u51fb\u53f3\u952e\uff0c\u5728\u60ac\u6d6e\u7684\u83dc\u5355\u76ee\u5f55\u4e2d\u70b9\u51fb\u3010Export...\u3011\uff0c\u683c\u5f0f\u9009\u62e9 HTTP Archive(.har) \u540e\u4fdd\u5b58\u5373\u53ef\uff1b\u5047\u8bbe\u6211\u4eec\u4fdd\u5b58\u7684\u6587\u4ef6\u540d\u79f0\u4e3a demo.har\u3002 \u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u7136\u540e\uff0c\u5728\u547d\u4ee4\u884c\u7ec8\u7aef\u4e2d\u8fd0\u884c har2case \u547d\u4ee4\uff0c\u5373\u53ef\u5c06 demo.har \u8f6c\u6362\u4e3a HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 \u4f7f\u7528 har2case \u8f6c\u6362\u811a\u672c\u65f6\u9ed8\u8ba4\u8f6c\u6362\u4e3a JSON \u683c\u5f0f\u3002 $ har2case docs/data/demo-quickstart.har INFO:root:Start to generate testcase. INFO:root:dump testcase to JSON format. INFO:root:Generate JSON testcase successfully: docs/data/demo-quickstart.json \u52a0\u4e0a -2y / --to-yml \u53c2\u6570\u540e\u8f6c\u6362\u4e3a YAML \u683c\u5f0f\u3002 $ har2case docs/data/demo-quickstart.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml \u4e24\u79cd\u683c\u5f0f\u5b8c\u5168\u7b49\u4ef7\uff0cYAML \u683c\u5f0f\u66f4\u7b80\u6d01\uff0cJSON \u683c\u5f0f\u652f\u6301\u7684\u5de5\u5177\u66f4\u4e30\u5bcc\uff0c\u5927\u5bb6\u53ef\u6839\u636e\u4e2a\u4eba\u559c\u597d\u8fdb\u884c\u9009\u62e9\u3002","title":"\u5f55\u5236\u751f\u6210\u7528\u4f8b"},{"location":"prepare/record/#har","text":"\u5728\u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b\u4e4b\u524d\uff0c\u9700\u8981\u5148\u5c06\u6293\u53d6\u5f97\u5230\u7684\u6570\u636e\u5305\u5bfc\u51fa\u4e3a HAR \u683c\u5f0f\u7684\u6587\u4ef6\u3002\u5728 Charles Proxy \u4e2d\u7684\u64cd\u4f5c\u65b9\u5f0f\u4e3a\uff0c\u9009\u4e2d\u9700\u8981\u8f6c\u6362\u7684\u63a5\u53e3\uff08\u53ef\u591a\u9009\u6216\u5168\u9009\uff09\uff0c\u70b9\u51fb\u53f3\u952e\uff0c\u5728\u60ac\u6d6e\u7684\u83dc\u5355\u76ee\u5f55\u4e2d\u70b9\u51fb\u3010Export...\u3011\uff0c\u683c\u5f0f\u9009\u62e9 HTTP Archive(.har) \u540e\u4fdd\u5b58\u5373\u53ef\uff1b\u5047\u8bbe\u6211\u4eec\u4fdd\u5b58\u7684\u6587\u4ef6\u540d\u79f0\u4e3a demo.har\u3002","title":"\u83b7\u53d6 HAR \u6570\u636e\u5305"},{"location":"prepare/record/#_1","text":"\u7136\u540e\uff0c\u5728\u547d\u4ee4\u884c\u7ec8\u7aef\u4e2d\u8fd0\u884c har2case \u547d\u4ee4\uff0c\u5373\u53ef\u5c06 demo.har \u8f6c\u6362\u4e3a HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002 \u4f7f\u7528 har2case \u8f6c\u6362\u811a\u672c\u65f6\u9ed8\u8ba4\u8f6c\u6362\u4e3a JSON \u683c\u5f0f\u3002 $ har2case docs/data/demo-quickstart.har INFO:root:Start to generate testcase. INFO:root:dump testcase to JSON format. INFO:root:Generate JSON testcase successfully: docs/data/demo-quickstart.json \u52a0\u4e0a -2y / --to-yml \u53c2\u6570\u540e\u8f6c\u6362\u4e3a YAML \u683c\u5f0f\u3002 $ har2case docs/data/demo-quickstart.har -2y INFO:root:Start to generate testcase. INFO:root:dump testcase to YAML format. INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml \u4e24\u79cd\u683c\u5f0f\u5b8c\u5168\u7b49\u4ef7\uff0cYAML \u683c\u5f0f\u66f4\u7b80\u6d01\uff0cJSON \u683c\u5f0f\u652f\u6301\u7684\u5de5\u5177\u66f4\u4e30\u5bcc\uff0c\u5927\u5bb6\u53ef\u6839\u636e\u4e2a\u4eba\u559c\u597d\u8fdb\u884c\u9009\u62e9\u3002","title":"\u8f6c\u6362\u751f\u6210\u6d4b\u8bd5\u7528\u4f8b"},{"location":"prepare/request-hook/","text":"\u6982\u8ff0 \u00b6 HttpRunner \u4ece 1.4.5 \u7248\u672c\u5f00\u59cb\u5b9e\u73b0\u4e86\u5168\u65b0\u7684 hook \u673a\u5236\uff0c\u53ef\u4ee5\u5728\u8bf7\u6c42\u524d\u548c\u8bf7\u6c42\u540e\u8c03\u7528\u94a9\u5b50\u51fd\u6570\u3002 \u8c03\u7528 hook \u51fd\u6570 \u00b6 hook \u673a\u5236\u5206\u4e3a\u4e24\u4e2a\u5c42\u7ea7\uff1a \u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\uff08testcase\uff09 \u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\uff08teststep\uff09 \u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\uff08testcase\uff09 \u00b6 \u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u7684 config \u4e2d\u65b0\u589e\u5173\u952e\u5b57 setup_hooks \u548c teardown_hooks \u3002 setup_hooks: \u5728\u6574\u4e2a\u7528\u4f8b\u5f00\u59cb\u6267\u884c\u524d\u89e6\u53d1 hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c\u3002 teardown_hooks: \u5728\u6574\u4e2a\u7528\u4f8b\u7ed3\u675f\u6267\u884c\u540e\u89e6\u53d1 hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c\u3002 - config : name : basic test with httpbin request : base_url : http://127.0.0.1:3458/ setup_hooks : - ${hook_print(setup)} teardown_hooks : - ${hook_print(teardown)} \u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\uff08teststep\uff09 \u00b6 \u5728 YAML/JSON \u6d4b\u8bd5\u6b65\u9aa4\u7684 test \u4e2d\u65b0\u589e\u5173\u952e\u5b57 setup_hooks \u548c teardown_hooks \u3002 setup_hooks: \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u524d\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c\uff1b\u4e5f\u53ef\u4ee5\u5b9e\u73b0\u5bf9\u8bf7\u6c42\u7684 request \u5185\u5bb9\u8fdb\u884c\u9884\u5904\u7406\u3002 teardown_hooks: \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u540e\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c\uff1b\u4e5f\u53ef\u4ee5\u5b9e\u73b0\u5bf9\u54cd\u5e94\u7684 response \u8fdb\u884c\u4fee\u6539\uff0c\u4f8b\u5982\u8fdb\u884c\u52a0\u89e3\u5bc6\u7b49\u5904\u7406\u3002 \"test\" : { \"name\" : \"get token with $user_agent, $os_platform, $app_version\" , \"request\" : { \"url\" : \"/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"app_version\" : \"$app_version\" , \"os_platform\" : \"$os_platform\" , \"user_agent\" : \"$user_agent\" }, \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" } }, \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]} ], \"setup_hooks\" : [ \"${setup_hook_prepare_kwargs($request)}\" , \"${setup_hook_httpntlmauth($request)}\" ], \"teardown_hooks\" : [ \"${teardown_hook_sleep_N_secs($response, 2)}\" ] } \u7f16\u5199 hook \u51fd\u6570 \u00b6 hook \u51fd\u6570\u7684\u5b9a\u4e49\u653e\u7f6e\u5728\u9879\u76ee\u7684 debugtalk.py \u4e2d\uff0c\u5728 YAML/JSON \u4e2d\u8c03\u7528 hook \u51fd\u6570\u4ecd\u7136\u662f\u91c7\u7528 ${func($a, $b)} \u7684\u5f62\u5f0f\u3002 \u5bf9\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\u7684 hook \u51fd\u6570\uff0c\u4e0e YAML/JSON \u4e2d\u81ea\u5b9a\u4e49\u7684\u51fd\u6570\u5b8c\u5168\u76f8\u540c\uff0c\u53ef\u901a\u8fc7\u81ea\u5b9a\u4e49\u53c2\u6570\u4f20\u53c2\u7684\u5f62\u5f0f\u6765\u5b9e\u73b0\u7075\u6d3b\u5e94\u7528\u3002 def hook_print ( msg ): print ( msg ) \u5bf9\u4e8e\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\u7684 hook \u51fd\u6570\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165\u4e0e\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u76f8\u5173\u7684\u4fe1\u606f\uff0c\u5305\u62ec\u8bf7\u6c42\u7684 $request \u548c\u54cd\u5e94\u7684 $response \uff0c\u7528\u4e8e\u5b9e\u73b0\u66f4\u590d\u6742\u573a\u666f\u7684\u7075\u6d3b\u5e94\u7528\u3002 setup_hooks \u00b6 \u5728\u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\u7684 setup_hooks \u51fd\u6570\u4e2d\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165 $request \uff0c\u8be5\u53c2\u6570\u5bf9\u5e94\u7740\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4 request \u7684\u5168\u90e8\u5185\u5bb9\u3002\u56e0\u4e3a request \u662f\u53ef\u53d8\u53c2\u6570\u7c7b\u578b\uff08dict\uff09\uff0c\u56e0\u6b64\u8be5\u51fd\u6570\u53c2\u6570\u4e3a\u5f15\u7528\u4f20\u9012\uff0c\u5f53\u6211\u4eec\u9700\u8981\u5bf9\u8bf7\u6c42\u53c2\u6570\u8fdb\u884c\u9884\u5904\u7406\u65f6\u5c24\u5176\u6709\u7528\u3002 e.g. def setup_hook_prepare_kwargs ( request ): if request [ \"method\" ] == \"POST\" : content_type = request . get ( \"headers\" , {}) . get ( \"content-type\" ) if content_type and \"data\" in request : # if request content-type is application/json, request data should be dumped if content_type . startswith ( \"application/json\" ) and isinstance ( request [ \"data\" ], ( dict , list )): request [ \"data\" ] = json . dumps ( request [ \"data\" ]) if isinstance ( request [ \"data\" ], str ): request [ \"data\" ] = request [ \"data\" ] . encode ( 'utf-8' ) def setup_hook_httpntlmauth ( request ): if \"httpntlmauth\" in request : from requests_ntlm import HttpNtlmAuth auth_account = request . pop ( \"httpntlmauth\" ) request [ \"auth\" ] = HttpNtlmAuth ( auth_account [ \"username\" ], auth_account [ \"password\" ]) \u901a\u8fc7\u4e0a\u8ff0\u7684 setup_hook_prepare_kwargs \u51fd\u6570\uff0c\u53ef\u4ee5\u5b9e\u73b0\u6839\u636e\u8bf7\u6c42\u65b9\u6cd5\u548c\u8bf7\u6c42\u7684 Content-Type \u6765\u5bf9\u8bf7\u6c42\u7684 data \u8fdb\u884c\u52a0\u5de5\u5904\u7406\uff1b\u901a\u8fc7 setup_hook_httpntlmauth \u51fd\u6570\uff0c\u53ef\u4ee5\u5b9e\u73b0 HttpNtlmAuth \u6743\u9650\u6388\u6743\u3002 teardown_hooks \u00b6 \u5728\u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\u7684 teardown_hooks \u51fd\u6570\u4e2d\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165 $response \uff0c\u8be5\u53c2\u6570\u5bf9\u5e94\u7740\u5f53\u524d\u8bf7\u6c42\u7684\u54cd\u5e94\u5b9e\u4f8b\uff08requests.Response\uff09\u3002 e.g. def teardown_hook_sleep_N_secs ( response , n_secs ): \"\"\" sleep n seconds after request \"\"\" if response . status_code == 200 : time . sleep ( 0.1 ) else : time . sleep ( n_secs ) \u901a\u8fc7\u4e0a\u8ff0\u7684 teardown_hook_sleep_N_secs \u51fd\u6570\uff0c\u53ef\u4ee5\u6839\u636e\u63a5\u53e3\u54cd\u5e94\u7684\u72b6\u6001\u7801\u6765\u8fdb\u884c\u4e0d\u540c\u65f6\u95f4\u7684\u5ef6\u8fdf\u7b49\u5f85\u3002 \u53e6\u5916\uff0c\u5728 teardown_hooks \u51fd\u6570\u4e2d\u8fd8\u53ef\u4ee5\u5bf9 response \u8fdb\u884c\u4fee\u6539\u3002\u5f53\u6211\u4eec\u9700\u8981\u5148\u5bf9\u54cd\u5e94\u5185\u5bb9\u8fdb\u884c\u5904\u7406\uff08\u4f8b\u5982\u52a0\u89e3\u5bc6\u3001\u53c2\u6570\u8fd0\u7b97\uff09\uff0c\u518d\u8fdb\u884c\u53c2\u6570\u63d0\u53d6\uff08extract\uff09\u548c\u6821\u9a8c\uff08validate\uff09\u65f6\u5c24\u5176\u6709\u7528\u3002 \u4f8b\u5982\u5728\u4e0b\u9762\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\uff0c\u5728\u6267\u884c\u6d4b\u8bd5\u540e\uff0c\u901a\u8fc7 teardown_hooks \u51fd\u6570\u5c06\u54cd\u5e94\u7ed3\u679c\u7684\u72b6\u6001\u7801\u548c headers \u8fdb\u884c\u4e86\u4fee\u6539\uff0c\u7136\u540e\u518d\u8fdb\u884c\u4e86\u6821\u9a8c\u3002 - test : name : alter response request : url : /headers method : GET teardown_hooks : - ${alter_response($response)} validate : - eq : [ \"status_code\" , 500 ] - eq : [ \"headers.content-type\" , \"html/text\" ] def alter_response ( response ): response . status_code = 500 response . headers [ \"Content-Type\" ] = \"html/text\"","title":"hook\u673a\u5236"},{"location":"prepare/request-hook/#_1","text":"HttpRunner \u4ece 1.4.5 \u7248\u672c\u5f00\u59cb\u5b9e\u73b0\u4e86\u5168\u65b0\u7684 hook \u673a\u5236\uff0c\u53ef\u4ee5\u5728\u8bf7\u6c42\u524d\u548c\u8bf7\u6c42\u540e\u8c03\u7528\u94a9\u5b50\u51fd\u6570\u3002","title":"\u6982\u8ff0"},{"location":"prepare/request-hook/#hook","text":"hook \u673a\u5236\u5206\u4e3a\u4e24\u4e2a\u5c42\u7ea7\uff1a \u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\uff08testcase\uff09 \u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\uff08teststep\uff09","title":"\u8c03\u7528 hook \u51fd\u6570"},{"location":"prepare/request-hook/#testcase","text":"\u5728 YAML/JSON \u6d4b\u8bd5\u7528\u4f8b\u7684 config \u4e2d\u65b0\u589e\u5173\u952e\u5b57 setup_hooks \u548c teardown_hooks \u3002 setup_hooks: \u5728\u6574\u4e2a\u7528\u4f8b\u5f00\u59cb\u6267\u884c\u524d\u89e6\u53d1 hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c\u3002 teardown_hooks: \u5728\u6574\u4e2a\u7528\u4f8b\u7ed3\u675f\u6267\u884c\u540e\u89e6\u53d1 hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c\u3002 - config : name : basic test with httpbin request : base_url : http://127.0.0.1:3458/ setup_hooks : - ${hook_print(setup)} teardown_hooks : - ${hook_print(teardown)}","title":"\u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\uff08testcase\uff09"},{"location":"prepare/request-hook/#teststep","text":"\u5728 YAML/JSON \u6d4b\u8bd5\u6b65\u9aa4\u7684 test \u4e2d\u65b0\u589e\u5173\u952e\u5b57 setup_hooks \u548c teardown_hooks \u3002 setup_hooks: \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u524d\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c\uff1b\u4e5f\u53ef\u4ee5\u5b9e\u73b0\u5bf9\u8bf7\u6c42\u7684 request \u5185\u5bb9\u8fdb\u884c\u9884\u5904\u7406\u3002 teardown_hooks: \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u540e\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c\uff1b\u4e5f\u53ef\u4ee5\u5b9e\u73b0\u5bf9\u54cd\u5e94\u7684 response \u8fdb\u884c\u4fee\u6539\uff0c\u4f8b\u5982\u8fdb\u884c\u52a0\u89e3\u5bc6\u7b49\u5904\u7406\u3002 \"test\" : { \"name\" : \"get token with $user_agent, $os_platform, $app_version\" , \"request\" : { \"url\" : \"/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"app_version\" : \"$app_version\" , \"os_platform\" : \"$os_platform\" , \"user_agent\" : \"$user_agent\" }, \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" } }, \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]} ], \"setup_hooks\" : [ \"${setup_hook_prepare_kwargs($request)}\" , \"${setup_hook_httpntlmauth($request)}\" ], \"teardown_hooks\" : [ \"${teardown_hook_sleep_N_secs($response, 2)}\" ] }","title":"\u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\uff08teststep\uff09"},{"location":"prepare/request-hook/#hook_1","text":"hook \u51fd\u6570\u7684\u5b9a\u4e49\u653e\u7f6e\u5728\u9879\u76ee\u7684 debugtalk.py \u4e2d\uff0c\u5728 YAML/JSON \u4e2d\u8c03\u7528 hook \u51fd\u6570\u4ecd\u7136\u662f\u91c7\u7528 ${func($a, $b)} \u7684\u5f62\u5f0f\u3002 \u5bf9\u4e8e\u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\u7684 hook \u51fd\u6570\uff0c\u4e0e YAML/JSON \u4e2d\u81ea\u5b9a\u4e49\u7684\u51fd\u6570\u5b8c\u5168\u76f8\u540c\uff0c\u53ef\u901a\u8fc7\u81ea\u5b9a\u4e49\u53c2\u6570\u4f20\u53c2\u7684\u5f62\u5f0f\u6765\u5b9e\u73b0\u7075\u6d3b\u5e94\u7528\u3002 def hook_print ( msg ): print ( msg ) \u5bf9\u4e8e\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u5c42\u9762\u7684 hook \u51fd\u6570\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165\u4e0e\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u76f8\u5173\u7684\u4fe1\u606f\uff0c\u5305\u62ec\u8bf7\u6c42\u7684 $request \u548c\u54cd\u5e94\u7684 $response \uff0c\u7528\u4e8e\u5b9e\u73b0\u66f4\u590d\u6742\u573a\u666f\u7684\u7075\u6d3b\u5e94\u7528\u3002","title":"\u7f16\u5199 hook \u51fd\u6570"},{"location":"prepare/request-hook/#setup_hooks","text":"\u5728\u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\u7684 setup_hooks \u51fd\u6570\u4e2d\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165 $request \uff0c\u8be5\u53c2\u6570\u5bf9\u5e94\u7740\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4 request \u7684\u5168\u90e8\u5185\u5bb9\u3002\u56e0\u4e3a request \u662f\u53ef\u53d8\u53c2\u6570\u7c7b\u578b\uff08dict\uff09\uff0c\u56e0\u6b64\u8be5\u51fd\u6570\u53c2\u6570\u4e3a\u5f15\u7528\u4f20\u9012\uff0c\u5f53\u6211\u4eec\u9700\u8981\u5bf9\u8bf7\u6c42\u53c2\u6570\u8fdb\u884c\u9884\u5904\u7406\u65f6\u5c24\u5176\u6709\u7528\u3002 e.g. def setup_hook_prepare_kwargs ( request ): if request [ \"method\" ] == \"POST\" : content_type = request . get ( \"headers\" , {}) . get ( \"content-type\" ) if content_type and \"data\" in request : # if request content-type is application/json, request data should be dumped if content_type . startswith ( \"application/json\" ) and isinstance ( request [ \"data\" ], ( dict , list )): request [ \"data\" ] = json . dumps ( request [ \"data\" ]) if isinstance ( request [ \"data\" ], str ): request [ \"data\" ] = request [ \"data\" ] . encode ( 'utf-8' ) def setup_hook_httpntlmauth ( request ): if \"httpntlmauth\" in request : from requests_ntlm import HttpNtlmAuth auth_account = request . pop ( \"httpntlmauth\" ) request [ \"auth\" ] = HttpNtlmAuth ( auth_account [ \"username\" ], auth_account [ \"password\" ]) \u901a\u8fc7\u4e0a\u8ff0\u7684 setup_hook_prepare_kwargs \u51fd\u6570\uff0c\u53ef\u4ee5\u5b9e\u73b0\u6839\u636e\u8bf7\u6c42\u65b9\u6cd5\u548c\u8bf7\u6c42\u7684 Content-Type \u6765\u5bf9\u8bf7\u6c42\u7684 data \u8fdb\u884c\u52a0\u5de5\u5904\u7406\uff1b\u901a\u8fc7 setup_hook_httpntlmauth \u51fd\u6570\uff0c\u53ef\u4ee5\u5b9e\u73b0 HttpNtlmAuth \u6743\u9650\u6388\u6743\u3002","title":"setup_hooks"},{"location":"prepare/request-hook/#teardown_hooks","text":"\u5728\u6d4b\u8bd5\u6b65\u9aa4\u5c42\u9762\u7684 teardown_hooks \u51fd\u6570\u4e2d\uff0c\u9664\u4e86\u53ef\u4f20\u5165\u81ea\u5b9a\u4e49\u53c2\u6570\u5916\uff0c\u8fd8\u53ef\u4ee5\u4f20\u5165 $response \uff0c\u8be5\u53c2\u6570\u5bf9\u5e94\u7740\u5f53\u524d\u8bf7\u6c42\u7684\u54cd\u5e94\u5b9e\u4f8b\uff08requests.Response\uff09\u3002 e.g. def teardown_hook_sleep_N_secs ( response , n_secs ): \"\"\" sleep n seconds after request \"\"\" if response . status_code == 200 : time . sleep ( 0.1 ) else : time . sleep ( n_secs ) \u901a\u8fc7\u4e0a\u8ff0\u7684 teardown_hook_sleep_N_secs \u51fd\u6570\uff0c\u53ef\u4ee5\u6839\u636e\u63a5\u53e3\u54cd\u5e94\u7684\u72b6\u6001\u7801\u6765\u8fdb\u884c\u4e0d\u540c\u65f6\u95f4\u7684\u5ef6\u8fdf\u7b49\u5f85\u3002 \u53e6\u5916\uff0c\u5728 teardown_hooks \u51fd\u6570\u4e2d\u8fd8\u53ef\u4ee5\u5bf9 response \u8fdb\u884c\u4fee\u6539\u3002\u5f53\u6211\u4eec\u9700\u8981\u5148\u5bf9\u54cd\u5e94\u5185\u5bb9\u8fdb\u884c\u5904\u7406\uff08\u4f8b\u5982\u52a0\u89e3\u5bc6\u3001\u53c2\u6570\u8fd0\u7b97\uff09\uff0c\u518d\u8fdb\u884c\u53c2\u6570\u63d0\u53d6\uff08extract\uff09\u548c\u6821\u9a8c\uff08validate\uff09\u65f6\u5c24\u5176\u6709\u7528\u3002 \u4f8b\u5982\u5728\u4e0b\u9762\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\uff0c\u5728\u6267\u884c\u6d4b\u8bd5\u540e\uff0c\u901a\u8fc7 teardown_hooks \u51fd\u6570\u5c06\u54cd\u5e94\u7ed3\u679c\u7684\u72b6\u6001\u7801\u548c headers \u8fdb\u884c\u4e86\u4fee\u6539\uff0c\u7136\u540e\u518d\u8fdb\u884c\u4e86\u6821\u9a8c\u3002 - test : name : alter response request : url : /headers method : GET teardown_hooks : - ${alter_response($response)} validate : - eq : [ \"status_code\" , 500 ] - eq : [ \"headers.content-type\" , \"html/text\" ] def alter_response ( response ): response . status_code = 500 response . headers [ \"Content-Type\" ] = \"html/text\"","title":"teardown_hooks"},{"location":"prepare/security/","text":"\u80cc\u666f \u00b6 \u5f88\u591a\u65f6\u5019\u9879\u76ee\u4ee3\u7801\u5728\u8fd0\u884c\u65f6\u9700\u8981\u4f7f\u7528\u5230\u8d26\u53f7\u3001\u5bc6\u7801\u3001key\u7b49\u654f\u611f\u6570\u636e\u4fe1\u606f\uff0c\u4f46\u662f\u4ece\u4fe1\u606f\u5b89\u5168\u7684\u89d2\u5ea6\u8003\u8651\uff0c\u6211\u4eec\u662f\u4e0d\u80fd\u5c06\u8fd9\u4e9b\u654f\u611f\u6570\u636e\u63d0\u4ea4\u5230\u4ee3\u7801\u4ed3\u5e93\u7684\uff0c\u4e3b\u8981\u539f\u56e0\u6709\u4e24\u4e2a\uff1a \u52a0\u5f3a\u6743\u9650\u7ba1\u63a7\uff1a\u53c2\u4e0e\u9879\u76ee\u7684\u5f00\u53d1\u4eba\u5458\u53ef\u80fd\u4f1a\u6709\u5f88\u591a\uff0c\u5927\u5bb6\u90fd\u6709\u8bfb\u53d6\u4ee3\u7801\u4ed3\u5e93\u7684\u6743\u9650\uff0c\u4f46\u662f\u50cf key \u8fd9\u7c7b\u6781\u5ea6\u654f\u611f\u7684\u4fe1\u606f\u4e0d\u5e94\u8be5\u6240\u6709\u4eba\u90fd\u6709\u6743\u9650\u83b7\u53d6\uff1b \u51cf\u5c11\u4ee3\u7801\u6cc4\u6f0f\u7684\u5371\u5bb3\u6027\uff1a\u5047\u5982\u4ee3\u7801\u51fa\u73b0\u6cc4\u6f0f\uff0c\u654f\u611f\u6570\u636e\u4fe1\u606f\u4e0d\u5e94\u8be5\u4e5f\u540c\u65f6\u6cc4\u6f0f\u3002 \u89e3\u51b3\u65b9\u6848 \u00b6 \u90a3\u4ee3\u7801\u90e8\u7f72\u5230\u670d\u52a1\u5668\u6216 Jenkins \u6267\u884c\u673a\u540e\uff0c\u8fd0\u884c\u65f6\u8981\u4f7f\u7528\u5230\u8fd9\u4e9b\u654f\u611f\u6570\u636e\u4fe1\u606f\uff0c\u8be5\u600e\u4e48\u64cd\u4f5c\u5462\uff1f \u63a8\u8350\u7684\u64cd\u4f5c\u65b9\u5f0f\u4e3a\uff1a \u5bf9\u670d\u52a1\u5668\u8fdb\u884c\u6743\u9650\u7ba1\u63a7\uff0c\u53ea\u6709\u8fd0\u7ef4\u4eba\u5458\uff08\u6216\u8005\u6838\u5fc3\u5f00\u53d1\u4eba\u5458\uff09\u624d\u6709\u767b\u5f55\u670d\u52a1\u5668\u7684\u6743\u9650\uff1b \u8fd0\u7ef4\u4eba\u5458\uff08\u6216\u8005\u6838\u5fc3\u5f00\u53d1\u4eba\u5458\uff09\uff1a\u5728\u8fd0\u884c\u7684\u673a\u5668\u4e0a\u5c06\u654f\u611f\u6570\u636e\u8bbe\u7f6e\u5230\u7cfb\u7edf\u7684\u73af\u5883\u53d8\u91cf\u4e2d\uff1b \u666e\u901a\u5f00\u53d1\u4eba\u5458\uff1a\u53ea\u9700\u8981\u77e5\u9053\u654f\u611f\u4fe1\u606f\u7684\u53d8\u91cf\u540d\u79f0\uff0c\u5728\u4ee3\u7801\u4e2d\u901a\u8fc7\u8bfb\u53d6\u73af\u5883\u53d8\u91cf\u7684\u65b9\u5f0f\u83b7\u53d6\u654f\u611f\u6570\u636e\u3002 \u5b58\u50a8\u654f\u611f\u6570\u636e\uff08\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf\uff09\u548c\u4f7f\u7528\u654f\u611f\u6570\u636e\uff08\u5f15\u7528\u73af\u5883\u53d8\u91cf\uff09\u7684\u5177\u4f53\u65b9\u6cd5\uff0c\u53ef\u53c2\u8003 \u73af\u5883\u53d8\u91cf \u4f7f\u7528\u8bf4\u660e\u6587\u6863\u3002","title":"\u4fe1\u606f\u5b89\u5168"},{"location":"prepare/security/#_1","text":"\u5f88\u591a\u65f6\u5019\u9879\u76ee\u4ee3\u7801\u5728\u8fd0\u884c\u65f6\u9700\u8981\u4f7f\u7528\u5230\u8d26\u53f7\u3001\u5bc6\u7801\u3001key\u7b49\u654f\u611f\u6570\u636e\u4fe1\u606f\uff0c\u4f46\u662f\u4ece\u4fe1\u606f\u5b89\u5168\u7684\u89d2\u5ea6\u8003\u8651\uff0c\u6211\u4eec\u662f\u4e0d\u80fd\u5c06\u8fd9\u4e9b\u654f\u611f\u6570\u636e\u63d0\u4ea4\u5230\u4ee3\u7801\u4ed3\u5e93\u7684\uff0c\u4e3b\u8981\u539f\u56e0\u6709\u4e24\u4e2a\uff1a \u52a0\u5f3a\u6743\u9650\u7ba1\u63a7\uff1a\u53c2\u4e0e\u9879\u76ee\u7684\u5f00\u53d1\u4eba\u5458\u53ef\u80fd\u4f1a\u6709\u5f88\u591a\uff0c\u5927\u5bb6\u90fd\u6709\u8bfb\u53d6\u4ee3\u7801\u4ed3\u5e93\u7684\u6743\u9650\uff0c\u4f46\u662f\u50cf key \u8fd9\u7c7b\u6781\u5ea6\u654f\u611f\u7684\u4fe1\u606f\u4e0d\u5e94\u8be5\u6240\u6709\u4eba\u90fd\u6709\u6743\u9650\u83b7\u53d6\uff1b \u51cf\u5c11\u4ee3\u7801\u6cc4\u6f0f\u7684\u5371\u5bb3\u6027\uff1a\u5047\u5982\u4ee3\u7801\u51fa\u73b0\u6cc4\u6f0f\uff0c\u654f\u611f\u6570\u636e\u4fe1\u606f\u4e0d\u5e94\u8be5\u4e5f\u540c\u65f6\u6cc4\u6f0f\u3002","title":"\u80cc\u666f"},{"location":"prepare/security/#_2","text":"\u90a3\u4ee3\u7801\u90e8\u7f72\u5230\u670d\u52a1\u5668\u6216 Jenkins \u6267\u884c\u673a\u540e\uff0c\u8fd0\u884c\u65f6\u8981\u4f7f\u7528\u5230\u8fd9\u4e9b\u654f\u611f\u6570\u636e\u4fe1\u606f\uff0c\u8be5\u600e\u4e48\u64cd\u4f5c\u5462\uff1f \u63a8\u8350\u7684\u64cd\u4f5c\u65b9\u5f0f\u4e3a\uff1a \u5bf9\u670d\u52a1\u5668\u8fdb\u884c\u6743\u9650\u7ba1\u63a7\uff0c\u53ea\u6709\u8fd0\u7ef4\u4eba\u5458\uff08\u6216\u8005\u6838\u5fc3\u5f00\u53d1\u4eba\u5458\uff09\u624d\u6709\u767b\u5f55\u670d\u52a1\u5668\u7684\u6743\u9650\uff1b \u8fd0\u7ef4\u4eba\u5458\uff08\u6216\u8005\u6838\u5fc3\u5f00\u53d1\u4eba\u5458\uff09\uff1a\u5728\u8fd0\u884c\u7684\u673a\u5668\u4e0a\u5c06\u654f\u611f\u6570\u636e\u8bbe\u7f6e\u5230\u7cfb\u7edf\u7684\u73af\u5883\u53d8\u91cf\u4e2d\uff1b \u666e\u901a\u5f00\u53d1\u4eba\u5458\uff1a\u53ea\u9700\u8981\u77e5\u9053\u654f\u611f\u4fe1\u606f\u7684\u53d8\u91cf\u540d\u79f0\uff0c\u5728\u4ee3\u7801\u4e2d\u901a\u8fc7\u8bfb\u53d6\u73af\u5883\u53d8\u91cf\u7684\u65b9\u5f0f\u83b7\u53d6\u654f\u611f\u6570\u636e\u3002 \u5b58\u50a8\u654f\u611f\u6570\u636e\uff08\u8bbe\u7f6e\u73af\u5883\u53d8\u91cf\uff09\u548c\u4f7f\u7528\u654f\u611f\u6570\u636e\uff08\u5f15\u7528\u73af\u5883\u53d8\u91cf\uff09\u7684\u5177\u4f53\u65b9\u6cd5\uff0c\u53ef\u53c2\u8003 \u73af\u5883\u53d8\u91cf \u4f7f\u7528\u8bf4\u660e\u6587\u6863\u3002","title":"\u89e3\u51b3\u65b9\u6848"},{"location":"prepare/testcase-layer/","text":"\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b \u00b6 \u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u9886\u57df\uff0c\u81ea\u52a8\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u53ef\u7ef4\u62a4\u6027\u662f\u6781\u5176\u91cd\u8981\u7684\u56e0\u7d20\uff0c\u76f4\u63a5\u5173\u7cfb\u5230\u81ea\u52a8\u5316\u6d4b\u8bd5\u80fd\u5426\u6301\u7eed\u6709\u6548\u5730\u5728\u9879\u76ee\u4e2d\u5f00\u5c55\u3002 \u6982\u62ec\u6765\u8bf4\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\u7684\u6838\u5fc3\u662f\u5c06\u63a5\u53e3\u5b9a\u4e49\u3001\u6d4b\u8bd5\u6b65\u9aa4\u3001\u6d4b\u8bd5\u7528\u4f8b\u3001\u6d4b\u8bd5\u573a\u666f\u8fdb\u884c\u5206\u79bb\uff0c\u5355\u72ec\u8fdb\u884c\u63cf\u8ff0\u548c\u7ef4\u62a4\uff0c\u4ece\u800c\u5c3d\u53ef\u80fd\u5730\u51cf\u5c11\u81ea\u52a8\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u7ef4\u62a4\u6210\u672c\u3002 \u903b\u8f91\u5173\u7cfb\u56fe\u5982\u4e0b\u6240\u793a\uff1a \u540c\u65f6\uff0c\u5f3a\u8c03\u5982\u4e0b\u51e0\u70b9\u6838\u5fc3\u6982\u5ff5\uff1a \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5e94\u8be5\u662f\u5b8c\u6574\u4e14\u72ec\u7acb\u7684\uff0c\u6bcf\u6761\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u90fd\u53ef\u4ee5\u72ec\u7acb\u8fd0\u884c\u7684 \u6d4b\u8bd5\u7528\u4f8b\u662f\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u7684 \u6709\u5e8f \u96c6\u5408\uff0c\u6bcf\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5bf9\u5e94\u4e00\u4e2a API \u7684\u8bf7\u6c42\u63cf\u8ff0 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u662f\u6d4b\u8bd5\u7528\u4f8b\u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\uff1b\u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\uff0c\u90a3\u5c31\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b8c\u6210\u4f9d\u8d56\u7684\u5904\u7406 \u5982\u679c\u5bf9\u4e8e\u4e0a\u8ff0\u7b2c\u4e09\u70b9\u611f\u89c9\u96be\u4ee5\u7406\u89e3\uff0c\u4e0d\u59a8\u770b\u4e0b\u4e0a\u56fe\u4e2d\u7684\u793a\u4f8b\uff1a testcase1 \u4f9d\u8d56\u4e8e testcase2\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep12\uff09\u4e2d\u5bf9 testcase2 \u8fdb\u884c\u5f15\u7528\uff0c\u7136\u540e testcase1 \u5c31\u662f\u5b8c\u6574\u4e14\u53ef\u72ec\u7acb\u8fd0\u884c\u7684\uff1b \u5728 testsuite \u4e2d\uff0ctestcase1 \u4e0e testcase2 \u76f8\u4e92\u72ec\u7acb\uff0c\u8fd0\u884c\u987a\u5e8f\u5c31\u4e0d\u518d\u6709\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u4e86\u3002 \u5206\u5c42\u63cf\u8ff0\u8be6\u89e3 \u00b6 \u7406\u89e3\u4e86\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u518d\u6765\u770b\u4e0b\u5728\u5206\u5c42\u6a21\u578b\u4e0b\uff0c\u63a5\u53e3\u3001\u6d4b\u8bd5\u7528\u4f8b\u3001\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u63cf\u8ff0\u5f62\u5f0f\u3002 \u63a5\u53e3\u5b9a\u4e49\uff08API\uff09 \u00b6 \u4e3a\u4e86\u66f4\u597d\u5730\u5bf9\u63a5\u53e3\u63cf\u8ff0\u8fdb\u884c\u7ba1\u7406\uff0c\u63a8\u8350\u4f7f\u7528\u72ec\u7acb\u7684\u6587\u4ef6\u5bf9\u63a5\u53e3\u63cf\u8ff0\u8fdb\u884c\u5b58\u50a8\uff0c\u5373\u6bcf\u4e2a\u6587\u4ef6\u5bf9\u5e94\u4e00\u4e2a\u63a5\u53e3\u63cf\u8ff0\u3002 \u63a5\u53e3\u5b9a\u4e49\u63cf\u8ff0\u7684\u4e3b\u8981\u5185\u5bb9\u5305\u62ec\uff1a name \u3001variables\u3001 request \u3001base_url\u3001validate \u7b49\uff0c\u5f62\u5f0f\u5982\u4e0b\uff1a name : get headers base_url : http://httpbin.org variables : expected_status_code : 200 request : url : /headers method : GET validate : - eq : [ \"status_code\" , $expected_status_code ] - eq : [ content.headers.Host , \"httpbin.org\" ] \u5176\u4e2d\uff0cname \u548c request \u90e8\u5206\u662f\u5fc5\u987b\u7684\uff0crequest \u4e2d\u7684\u63cf\u8ff0\u5f62\u5f0f\u4e0e requests.request \u5b8c\u5168\u76f8\u540c\u3002 \u53e6\u5916\uff0cAPI \u63cf\u8ff0\u9700\u8981\u5c3d\u91cf\u4fdd\u6301\u5b8c\u6574\uff0c\u505a\u5230\u53ef\u4ee5\u5355\u72ec\u8fd0\u884c\u3002\u5982\u679c\u5728\u63a5\u53e3\u63cf\u8ff0\u4e2d\u5b58\u5728\u53d8\u91cf\u5f15\u7528\u7684\u60c5\u51b5\uff0c\u53ef\u5728 variables \u4e2d\u5bf9\u53c2\u6570\u8fdb\u884c\u5b9a\u4e49\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u53ef\u4ee5\u5f88\u597d\u5730\u5b9e\u73b0\u5355\u4e2a\u63a5\u53e3\u7684\u8c03\u8bd5\u3002 $ hrun api/get_headers.yml INFO Start to run testcase: get headers headers INFO GET http://httpbin.org/headers INFO status_code: 200 , response_time ( ms ) : 477 .32 ms, response_length: 157 bytes . ---------------------------------------------------------------------- Ran 1 test in 0 .478s OK \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09 \u00b6 \u5f15\u7528\u63a5\u53e3\u5b9a\u4e49 \u00b6 \u6709\u4e86\u63a5\u53e3\u7684\u5b9a\u4e49\u63cf\u8ff0\u540e\uff0c\u6211\u4eec\u7f16\u5199\u6d4b\u8bd5\u573a\u666f\u65f6\u5c31\u53ef\u4ee5\u76f4\u63a5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\u4e86\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u4e2d\uff0c\u53ef\u901a\u8fc7 api \u5b57\u6bb5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\uff0c\u5f15\u7528\u65b9\u5f0f\u4e3a\u5bf9\u5e94 API \u6587\u4ef6\u7684\u8def\u5f84\uff0c\u7edd\u5bf9\u8def\u5f84\u6216\u76f8\u5bf9\u8def\u5f84\u5747\u53ef\u3002\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\uff0c\u8def\u5f84\u57fa\u51c6\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\uff0c\u5373 debugtalk.py \u6240\u5728\u7684\u76ee\u5f55\u8def\u5f84\u3002 - config : name : \"setup and reset all.\" variables : user_agent : 'iOS/10.3' device_sn : \"TESTCASE_SETUP_XXX\" os_platform : 'ios' app_version : '2.8.6' base_url : \"http://127.0.0.1:5000\" verify : False output : - session_token - test : name : get token (setup) api : api/get_token.yml variables : user_agent : 'iOS/10.3' device_sn : $device_sn os_platform : 'ios' app_version : '2.8.6' extract : - session_token : content.token validate : - eq : [ \"status_code\" , 200 ] - len_eq : [ \"content.token\" , 16 ] - test : name : reset all users api : api/reset_all.yml variables : token : $session_token \u82e5\u9700\u8981\u63a7\u5236\u6216\u6539\u53d8\u63a5\u53e3\u5b9a\u4e49\u4e2d\u7684\u53c2\u6570\u503c\uff0c\u53ef\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u6307\u5b9a variables \u53c2\u6570\uff0c\u8986\u76d6 API \u4e2d\u7684 variables \u5b9e\u73b0\u3002 \u540c\u6837\u5730\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5b9a\u4e49 validate \u540e\uff0c\u4e5f\u4f1a\u4e0e API \u4e2d\u7684 validate \u5408\u5e76\u8986\u76d6\u3002\u56e0\u6b64\u63a8\u8350\u7684\u505a\u6cd5\u662f\uff0c\u5728 API \u5b9a\u4e49\u4e2d\u7684 validate \u53ea\u63cf\u8ff0\u6700\u57fa\u672c\u7684\u6821\u9a8c\u9879\uff0c\u4f8b\u5982 status_code\uff0c\u5bf9\u4e8e\u4e0e\u4e1a\u52a1\u903b\u8f91\u76f8\u5173\u7684\u66f4\u591a\u6821\u9a8c\u9879\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u7684 validate \u4e2d\u8fdb\u884c\u63cf\u8ff0\u3002 \u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b \u00b6 \u5728\u6d4b\u8bd5\u7528\u4f8b\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\uff0c\u9664\u4e86\u53ef\u4ee5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\uff0c\u8fd8\u53ef\u4ee5\u5f15\u7528\u5176\u5b83\u6d4b\u8bd5\u7528\u4f8b\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u53ef\u4ee5\u5728\u907f\u514d\u91cd\u590d\u63cf\u8ff0\u7684\u540c\u65f6\uff0c\u89e3\u51b3\u6d4b\u8bd5\u7528\u4f8b\u7684\u4f9d\u8d56\u5173\u7cfb\uff0c\u4ece\u800c\u4fdd\u8bc1\u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u90fd\u662f\u72ec\u7acb\u53ef\u8fd0\u884c\u7684\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u4e2d\uff0c\u53ef\u901a\u8fc7 testcase \u5b57\u6bb5\u5f15\u7528\u5176\u5b83\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5f15\u7528\u65b9\u5f0f\u4e3a\u5bf9\u5e94\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\uff0c\u7edd\u5bf9\u8def\u5f84\u6216\u76f8\u5bf9\u8def\u5f84\u5747\u53ef\u3002\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\uff0c\u8def\u5f84\u57fa\u51c6\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\uff0c\u5373 debugtalk.py \u6240\u5728\u7684\u76ee\u5f55\u8def\u5f84\u3002 \u4f8b\u5982\uff0c\u5728\u4e0a\u9762\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08\"setup and reset all.\"\uff09\u4e2d\uff0c\u5b9e\u73b0\u4e86\u5bf9\u83b7\u53d6 token \u529f\u80fd\u7684\u6d4b\u8bd5\uff1b\u540c\u65f6\uff0c\u5728\u5f88\u591a\u5176\u5b83\u529f\u80fd\u4e2d\u90fd\u4f1a\u4f9d\u8d56\u4e8e\u83b7\u53d6 token \u7684\u529f\u80fd\uff0c\u5982\u679c\u5c06\u8be5\u529f\u80fd\u7684\u6d4b\u8bd5\u6b65\u9aa4\u811a\u672c\u62f7\u8d1d\u5230\u5176\u5b83\u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e2d\uff0c\u90a3\u4e48\u5c31\u4f1a\u5b58\u5728\u5927\u91cf\u91cd\u590d\uff0c\u5f53\u9700\u8981\u5bf9\u8be5\u90e8\u5206\u8fdb\u884c\u4fee\u6539\u65f6\u5c31\u9700\u8981\u4fee\u6539\u6240\u6709\u5730\u65b9\uff0c\u663e\u7136\u4e0d\u4fbf\u4e8e\u7ef4\u62a4\u3002 \u6bd4\u8f83\u597d\u7684\u505a\u6cd5\u662f\uff0c\u5728\u5176\u5b83\u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08\u5982\u521b\u5efa\u7528\u6237\uff09\u4e2d\uff0c\u5f15\u7528\u83b7\u53d6 token \u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08testcases/setup.yml\uff09\u4f5c\u4e3a\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u4ece\u800c\u521b\u5efa\u7528\u6237\uff08\"create user and check result.\"\uff09\u8fd9\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u53d8\u5f97\u72ec\u7acb\u53ef\u8fd0\u884c\u4e86\u3002 - config : name : \"create user and check result.\" id : create_user base_url : \"http://127.0.0.1:5000\" variables : uid : 9001 device_sn : \"TESTCASE_CREATE_XXX\" output : - session_token - test : name : setup and reset all (override) for $device_sn. testcase : testcases/setup.yml output : - session_token - test : name : create user and check result. variables : token : $session_token testcase : testcases/deps/check_and_create.yml \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09 \u00b6 \u5f53\u6d4b\u8bd5\u7528\u4f8b\u6570\u91cf\u6bd4\u8f83\u591a\u4ee5\u540e\uff0c\u4e3a\u4e86\u65b9\u4fbf\u7ba1\u7406\u548c\u5b9e\u73b0\u6279\u91cf\u8fd0\u884c\uff0c\u901a\u5e38\u9700\u8981\u4f7f\u7528\u6d4b\u8bd5\u7528\u4f8b\u96c6\u6765\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u7ec4\u7ec7\u3002 \u5728\u524d\u6587\u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b\u4e2d\u4e5f\u5f3a\u8c03\u4e86\uff0c\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u662f\u6d4b\u8bd5\u7528\u4f8b\u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\uff1b\u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\uff0c\u90a3\u5c31\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b8c\u6210\u4f9d\u8d56\u7684\u5904\u7406\u3002 \u56e0\u4e3a\u662f \u65e0\u5e8f \u96c6\u5408\uff0c\u56e0\u6b64\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u63cf\u8ff0\u5f62\u5f0f\u4f1a\u4e0e\u6d4b\u8bd5\u7528\u4f8b\u6709\u4e9b\u4e0d\u540c\uff0c\u5728\u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u96c6\u6587\u4ef6\u4e2d\uff0c\u7b2c\u4e00\u5c42\u7ea7\u5b58\u5728\u4e24\u7c7b\u5b57\u6bb5\uff1a config: \u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u603b\u4f53\u914d\u7f6e\u53c2\u6570 testcases: \u503c\u4e3a\u5b57\u5178\u7ed3\u6784\uff08\u65e0\u5e8f\uff09\uff0ckey \u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u540d\u79f0\uff0cvalue \u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5185\u5bb9\uff1b\u5728\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\u65f6\u4e5f\u53ef\u4ee5\u6307\u5b9a variables\uff0c\u5b9e\u73b0\u5bf9\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\u4e2d variables \u7684\u8986\u76d6\u3002 \u975e\u53c2\u6570\u5316\u573a\u666f \u00b6 config : name : create users with uid variables : device_sn : ${gen_random_string(15)} var_a : ${gen_random_string(5)} var_b : $var_a base_url : \"http://127.0.0.1:5000\" testcases : create user 1000 and check result. : testcase : testcases/create_user.yml variables : uid : 1000 var_c : ${gen_random_string(5)} var_d : $var_c create user 1001 and check result. : testcase : testcases/create_user.yml variables : uid : 1001 var_c : ${gen_random_string(5)} var_d : $var_c \u53c2\u6570\u5316\u573a\u666f\uff08parameters\uff09 \u00b6 \u5bf9\u4e8e\u53c2\u6570\u5316\u573a\u666f\uff0c\u53ef\u901a\u8fc7 parameters \u5b9e\u73b0\uff0c\u63cf\u8ff0\u5f62\u5f0f\u5982\u4e0b\u6240\u793a\u3002 config : name : create users with parameters variables : device_sn : ${gen_random_string(15)} base_url : \"http://127.0.0.1:5000\" testcases : create user $uid and check result for $device_sn. : testcase : testcases/create_user.yml variables : uid : 1000 device_sn : TESTSUITE_XXX parameters : uid : [ 101 , 102 , 103 ] device_sn : [ TESTSUITE_X1 , TESTSUITE_X2 ] \u53c2\u6570\u5316\u540e\uff0cparameters \u4e2d\u7684\u53d8\u91cf\u5c06\u91c7\u7528\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\u5f62\u6210\u53c2\u6570\u5217\u8868\uff0c\u4f9d\u6b21\u8986\u76d6 variables \u4e2d\u7684\u53c2\u6570\uff0c\u9a71\u52a8\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u3002 \u6587\u4ef6\u76ee\u5f55\u7ed3\u6784\u7ba1\u7406 && \u811a\u624b\u67b6\u5de5\u5177 \u00b6 \u5728\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8fdb\u884c\u7ec4\u7ec7\u7ba1\u7406\u65f6\uff0c\u5bf9\u4e8e\u6587\u4ef6\u7684\u5b58\u50a8\u4f4d\u7f6e\u5747\u6ca1\u6709\u8981\u6c42\u548c\u9650\u5236\uff0c\u5728\u5f15\u7528\u65f6\u53ea\u9700\u8981\u6307\u5b9a\u5bf9\u5e94\u7684\u6587\u4ef6\u8def\u5f84\u5373\u53ef\u3002\u4f46\u4ece\u7ea6\u5b9a\u5927\u4e8e\u914d\u7f6e\u7684\u89d2\u5ea6\uff0c\u6700\u597d\u662f\u6309\u7167\u63a8\u8350\u7684\u6587\u4ef6\u5939\u540d\u79f0\u8fdb\u884c\u5b58\u50a8\u7ba1\u7406\uff0c\u5e76\u53ef\u901a\u8fc7\u5b50\u76ee\u5f55\u5b9e\u73b0\u9879\u76ee\u6a21\u5757\u5206\u7c7b\u7ba1\u7406\u3002 \u63a8\u8350\u7684\u65b9\u5f0f\u6c47\u603b\u5982\u4e0b\uff1a debugtalk.py \u653e\u7f6e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u5047\u8bbe\u4e3a PRJ_ROOT_DIR .env \u653e\u7f6e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u8def\u5f84\u4e3a PRJ_ROOT_DIR/.env \u63a5\u53e3\u5b9a\u4e49\uff08API\uff09\u653e\u7f6e\u5728 PRJ_ROOT_DIR/api/ \u76ee\u5f55\u4e0b \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u653e\u7f6e\u5728 PRJ_ROOT_DIR/testcases/ \u76ee\u5f55\u4e0b \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u6587\u4ef6\u5fc5\u987b\u653e\u7f6e\u5728 PRJ_ROOT_DIR/testsuites/ \u76ee\u5f55\u4e0b data \u6587\u4ef6\u5939\uff1a\u5b58\u50a8\u53c2\u6570\u5316\u6587\u4ef6\uff0c\u6216\u8005\u9879\u76ee\u4f9d\u8d56\u7684\u6587\u4ef6\uff0c\u8def\u5f84\u4e3a PRJ_ROOT_DIR/data/ reports \u6587\u4ef6\u5939\uff1a\u5b58\u50a8 \bHTML \u6d4b\u8bd5\u62a5\u544a\uff0c\u751f\u6210\u8def\u5f84\u4e3a PRJ_ROOT_DIR/reports/ \u76ee\u5f55\u7ed3\u6784\u5982\u4e0b\u6240\u793a\uff1a $ tree tests tests \u251c\u2500\u2500 .env \u251c\u2500\u2500 data \u2502 \u251c\u2500\u2500 app_version.csv \u2502 \u2514\u2500\u2500 account.csv \u251c\u2500\u2500 api \u2502 \u251c\u2500\u2500 create_user.yml \u2502 \u251c\u2500\u2500 get_headers.yml \u2502 \u251c\u2500\u2500 get_token.yml \u2502 \u251c\u2500\u2500 get_user.yml \u2502 \u2514\u2500\u2500 reset_all.yml \u251c\u2500\u2500 debugtalk.py \u251c\u2500\u2500 testcases \u2502 \u251c\u2500\u2500 create_user.yml \u2502 \u251c\u2500\u2500 deps \u2502 \u2502 \u2514\u2500\u2500 check_and_create.yml \u2502 \u2514\u2500\u2500 setup.yml \u2514\u2500\u2500 testsuites \u251c\u2500\u2500 create_users.yml \u2514\u2500\u2500 create_users_with_parameters.yml \u9879\u76ee\u811a\u624b\u67b6 \u540c\u65f6\uff0c\u5728 HttpRunner \u4e2d\u5b9e\u73b0\u4e86\u4e00\u4e2a\u811a\u624b\u67b6\u5de5\u5177\uff0c\u53ef\u4ee5\u5feb\u901f\u521b\u5efa\u9879\u76ee\u7684\u76ee\u5f55\u7ed3\u6784\u3002\u8be5\u60f3\u6cd5\u6765\u6e90\u4e8e Django \u7684 django-admin.py startproject project_name \u3002 \u4f7f\u7528\u65b9\u5f0f\u4e5f\u4e0e Django \u7c7b\u4f3c\uff0c\u53ea\u9700\u8981\u901a\u8fc7 --startproject \u6307\u5b9a\u65b0\u9879\u76ee\u7684\u540d\u79f0\u5373\u53ef\u3002 $ hrun --startproject demo Start to create new project: demo CWD: /Users/debugtalk/MyProjects/examples created folder: demo created folder: demo/api created folder: demo/testcases created folder: demo/testsuites created folder: demo/reports created file: demo/debugtalk.py created file: demo/.env \u76f8\u5173\u53c2\u8003 \u00b6 \u300aHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\uff08\u5df2\u8fc7\u671f\uff09\u300b \u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u8be6\u7ec6\u793a\u4f8b\uff1a HttpRunner/tests","title":"\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42"},{"location":"prepare/testcase-layer/#_1","text":"\u5728\u81ea\u52a8\u5316\u6d4b\u8bd5\u9886\u57df\uff0c\u81ea\u52a8\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u53ef\u7ef4\u62a4\u6027\u662f\u6781\u5176\u91cd\u8981\u7684\u56e0\u7d20\uff0c\u76f4\u63a5\u5173\u7cfb\u5230\u81ea\u52a8\u5316\u6d4b\u8bd5\u80fd\u5426\u6301\u7eed\u6709\u6548\u5730\u5728\u9879\u76ee\u4e2d\u5f00\u5c55\u3002 \u6982\u62ec\u6765\u8bf4\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\u7684\u6838\u5fc3\u662f\u5c06\u63a5\u53e3\u5b9a\u4e49\u3001\u6d4b\u8bd5\u6b65\u9aa4\u3001\u6d4b\u8bd5\u7528\u4f8b\u3001\u6d4b\u8bd5\u573a\u666f\u8fdb\u884c\u5206\u79bb\uff0c\u5355\u72ec\u8fdb\u884c\u63cf\u8ff0\u548c\u7ef4\u62a4\uff0c\u4ece\u800c\u5c3d\u53ef\u80fd\u5730\u51cf\u5c11\u81ea\u52a8\u5316\u6d4b\u8bd5\u7528\u4f8b\u7684\u7ef4\u62a4\u6210\u672c\u3002 \u903b\u8f91\u5173\u7cfb\u56fe\u5982\u4e0b\u6240\u793a\uff1a \u540c\u65f6\uff0c\u5f3a\u8c03\u5982\u4e0b\u51e0\u70b9\u6838\u5fc3\u6982\u5ff5\uff1a \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u5e94\u8be5\u662f\u5b8c\u6574\u4e14\u72ec\u7acb\u7684\uff0c\u6bcf\u6761\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u662f\u90fd\u53ef\u4ee5\u72ec\u7acb\u8fd0\u884c\u7684 \u6d4b\u8bd5\u7528\u4f8b\u662f\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u7684 \u6709\u5e8f \u96c6\u5408\uff0c\u6bcf\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\u5bf9\u5e94\u4e00\u4e2a API \u7684\u8bf7\u6c42\u63cf\u8ff0 \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u662f\u6d4b\u8bd5\u7528\u4f8b\u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\uff1b\u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\uff0c\u90a3\u5c31\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b8c\u6210\u4f9d\u8d56\u7684\u5904\u7406 \u5982\u679c\u5bf9\u4e8e\u4e0a\u8ff0\u7b2c\u4e09\u70b9\u611f\u89c9\u96be\u4ee5\u7406\u89e3\uff0c\u4e0d\u59a8\u770b\u4e0b\u4e0a\u56fe\u4e2d\u7684\u793a\u4f8b\uff1a testcase1 \u4f9d\u8d56\u4e8e testcase2\uff0c\u90a3\u4e48\u5c31\u53ef\u4ee5\u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep12\uff09\u4e2d\u5bf9 testcase2 \u8fdb\u884c\u5f15\u7528\uff0c\u7136\u540e testcase1 \u5c31\u662f\u5b8c\u6574\u4e14\u53ef\u72ec\u7acb\u8fd0\u884c\u7684\uff1b \u5728 testsuite \u4e2d\uff0ctestcase1 \u4e0e testcase2 \u76f8\u4e92\u72ec\u7acb\uff0c\u8fd0\u884c\u987a\u5e8f\u5c31\u4e0d\u518d\u6709\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u4e86\u3002","title":"\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b"},{"location":"prepare/testcase-layer/#_2","text":"\u7406\u89e3\u4e86\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b\uff0c\u63a5\u4e0b\u6765\u6211\u4eec\u518d\u6765\u770b\u4e0b\u5728\u5206\u5c42\u6a21\u578b\u4e0b\uff0c\u63a5\u53e3\u3001\u6d4b\u8bd5\u7528\u4f8b\u3001\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u63cf\u8ff0\u5f62\u5f0f\u3002","title":"\u5206\u5c42\u63cf\u8ff0\u8be6\u89e3"},{"location":"prepare/testcase-layer/#api","text":"\u4e3a\u4e86\u66f4\u597d\u5730\u5bf9\u63a5\u53e3\u63cf\u8ff0\u8fdb\u884c\u7ba1\u7406\uff0c\u63a8\u8350\u4f7f\u7528\u72ec\u7acb\u7684\u6587\u4ef6\u5bf9\u63a5\u53e3\u63cf\u8ff0\u8fdb\u884c\u5b58\u50a8\uff0c\u5373\u6bcf\u4e2a\u6587\u4ef6\u5bf9\u5e94\u4e00\u4e2a\u63a5\u53e3\u63cf\u8ff0\u3002 \u63a5\u53e3\u5b9a\u4e49\u63cf\u8ff0\u7684\u4e3b\u8981\u5185\u5bb9\u5305\u62ec\uff1a name \u3001variables\u3001 request \u3001base_url\u3001validate \u7b49\uff0c\u5f62\u5f0f\u5982\u4e0b\uff1a name : get headers base_url : http://httpbin.org variables : expected_status_code : 200 request : url : /headers method : GET validate : - eq : [ \"status_code\" , $expected_status_code ] - eq : [ content.headers.Host , \"httpbin.org\" ] \u5176\u4e2d\uff0cname \u548c request \u90e8\u5206\u662f\u5fc5\u987b\u7684\uff0crequest \u4e2d\u7684\u63cf\u8ff0\u5f62\u5f0f\u4e0e requests.request \u5b8c\u5168\u76f8\u540c\u3002 \u53e6\u5916\uff0cAPI \u63cf\u8ff0\u9700\u8981\u5c3d\u91cf\u4fdd\u6301\u5b8c\u6574\uff0c\u505a\u5230\u53ef\u4ee5\u5355\u72ec\u8fd0\u884c\u3002\u5982\u679c\u5728\u63a5\u53e3\u63cf\u8ff0\u4e2d\u5b58\u5728\u53d8\u91cf\u5f15\u7528\u7684\u60c5\u51b5\uff0c\u53ef\u5728 variables \u4e2d\u5bf9\u53c2\u6570\u8fdb\u884c\u5b9a\u4e49\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u53ef\u4ee5\u5f88\u597d\u5730\u5b9e\u73b0\u5355\u4e2a\u63a5\u53e3\u7684\u8c03\u8bd5\u3002 $ hrun api/get_headers.yml INFO Start to run testcase: get headers headers INFO GET http://httpbin.org/headers INFO status_code: 200 , response_time ( ms ) : 477 .32 ms, response_length: 157 bytes . ---------------------------------------------------------------------- Ran 1 test in 0 .478s OK","title":"\u63a5\u53e3\u5b9a\u4e49\uff08API\uff09"},{"location":"prepare/testcase-layer/#testcase","text":"","title":"\u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09"},{"location":"prepare/testcase-layer/#_3","text":"\u6709\u4e86\u63a5\u53e3\u7684\u5b9a\u4e49\u63cf\u8ff0\u540e\uff0c\u6211\u4eec\u7f16\u5199\u6d4b\u8bd5\u573a\u666f\u65f6\u5c31\u53ef\u4ee5\u76f4\u63a5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\u4e86\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u4e2d\uff0c\u53ef\u901a\u8fc7 api \u5b57\u6bb5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\uff0c\u5f15\u7528\u65b9\u5f0f\u4e3a\u5bf9\u5e94 API \u6587\u4ef6\u7684\u8def\u5f84\uff0c\u7edd\u5bf9\u8def\u5f84\u6216\u76f8\u5bf9\u8def\u5f84\u5747\u53ef\u3002\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\uff0c\u8def\u5f84\u57fa\u51c6\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\uff0c\u5373 debugtalk.py \u6240\u5728\u7684\u76ee\u5f55\u8def\u5f84\u3002 - config : name : \"setup and reset all.\" variables : user_agent : 'iOS/10.3' device_sn : \"TESTCASE_SETUP_XXX\" os_platform : 'ios' app_version : '2.8.6' base_url : \"http://127.0.0.1:5000\" verify : False output : - session_token - test : name : get token (setup) api : api/get_token.yml variables : user_agent : 'iOS/10.3' device_sn : $device_sn os_platform : 'ios' app_version : '2.8.6' extract : - session_token : content.token validate : - eq : [ \"status_code\" , 200 ] - len_eq : [ \"content.token\" , 16 ] - test : name : reset all users api : api/reset_all.yml variables : token : $session_token \u82e5\u9700\u8981\u63a7\u5236\u6216\u6539\u53d8\u63a5\u53e3\u5b9a\u4e49\u4e2d\u7684\u53c2\u6570\u503c\uff0c\u53ef\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u6307\u5b9a variables \u53c2\u6570\uff0c\u8986\u76d6 API \u4e2d\u7684 variables \u5b9e\u73b0\u3002 \u540c\u6837\u5730\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5b9a\u4e49 validate \u540e\uff0c\u4e5f\u4f1a\u4e0e API \u4e2d\u7684 validate \u5408\u5e76\u8986\u76d6\u3002\u56e0\u6b64\u63a8\u8350\u7684\u505a\u6cd5\u662f\uff0c\u5728 API \u5b9a\u4e49\u4e2d\u7684 validate \u53ea\u63cf\u8ff0\u6700\u57fa\u672c\u7684\u6821\u9a8c\u9879\uff0c\u4f8b\u5982 status_code\uff0c\u5bf9\u4e8e\u4e0e\u4e1a\u52a1\u903b\u8f91\u76f8\u5173\u7684\u66f4\u591a\u6821\u9a8c\u9879\uff0c\u5728\u6d4b\u8bd5\u6b65\u9aa4\u7684 validate \u4e2d\u8fdb\u884c\u63cf\u8ff0\u3002","title":"\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49"},{"location":"prepare/testcase-layer/#_4","text":"\u5728\u6d4b\u8bd5\u7528\u4f8b\u7684\u6d4b\u8bd5\u6b65\u9aa4\u4e2d\uff0c\u9664\u4e86\u53ef\u4ee5\u5f15\u7528\u63a5\u53e3\u5b9a\u4e49\uff0c\u8fd8\u53ef\u4ee5\u5f15\u7528\u5176\u5b83\u6d4b\u8bd5\u7528\u4f8b\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u53ef\u4ee5\u5728\u907f\u514d\u91cd\u590d\u63cf\u8ff0\u7684\u540c\u65f6\uff0c\u89e3\u51b3\u6d4b\u8bd5\u7528\u4f8b\u7684\u4f9d\u8d56\u5173\u7cfb\uff0c\u4ece\u800c\u4fdd\u8bc1\u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u90fd\u662f\u72ec\u7acb\u53ef\u8fd0\u884c\u7684\u3002 \u5728\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\u4e2d\uff0c\u53ef\u901a\u8fc7 testcase \u5b57\u6bb5\u5f15\u7528\u5176\u5b83\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5f15\u7528\u65b9\u5f0f\u4e3a\u5bf9\u5e94\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\uff0c\u7edd\u5bf9\u8def\u5f84\u6216\u76f8\u5bf9\u8def\u5f84\u5747\u53ef\u3002\u63a8\u8350\u4f7f\u7528\u76f8\u5bf9\u8def\u5f84\uff0c\u8def\u5f84\u57fa\u51c6\u4e3a\u9879\u76ee\u6839\u76ee\u5f55\uff0c\u5373 debugtalk.py \u6240\u5728\u7684\u76ee\u5f55\u8def\u5f84\u3002 \u4f8b\u5982\uff0c\u5728\u4e0a\u9762\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08\"setup and reset all.\"\uff09\u4e2d\uff0c\u5b9e\u73b0\u4e86\u5bf9\u83b7\u53d6 token \u529f\u80fd\u7684\u6d4b\u8bd5\uff1b\u540c\u65f6\uff0c\u5728\u5f88\u591a\u5176\u5b83\u529f\u80fd\u4e2d\u90fd\u4f1a\u4f9d\u8d56\u4e8e\u83b7\u53d6 token \u7684\u529f\u80fd\uff0c\u5982\u679c\u5c06\u8be5\u529f\u80fd\u7684\u6d4b\u8bd5\u6b65\u9aa4\u811a\u672c\u62f7\u8d1d\u5230\u5176\u5b83\u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e2d\uff0c\u90a3\u4e48\u5c31\u4f1a\u5b58\u5728\u5927\u91cf\u91cd\u590d\uff0c\u5f53\u9700\u8981\u5bf9\u8be5\u90e8\u5206\u8fdb\u884c\u4fee\u6539\u65f6\u5c31\u9700\u8981\u4fee\u6539\u6240\u6709\u5730\u65b9\uff0c\u663e\u7136\u4e0d\u4fbf\u4e8e\u7ef4\u62a4\u3002 \u6bd4\u8f83\u597d\u7684\u505a\u6cd5\u662f\uff0c\u5728\u5176\u5b83\u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08\u5982\u521b\u5efa\u7528\u6237\uff09\u4e2d\uff0c\u5f15\u7528\u83b7\u53d6 token \u529f\u80fd\u7684\u6d4b\u8bd5\u7528\u4f8b\uff08testcases/setup.yml\uff09\u4f5c\u4e3a\u4e00\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff0c\u4ece\u800c\u521b\u5efa\u7528\u6237\uff08\"create user and check result.\"\uff09\u8fd9\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u53d8\u5f97\u72ec\u7acb\u53ef\u8fd0\u884c\u4e86\u3002 - config : name : \"create user and check result.\" id : create_user base_url : \"http://127.0.0.1:5000\" variables : uid : 9001 device_sn : \"TESTCASE_CREATE_XXX\" output : - session_token - test : name : setup and reset all (override) for $device_sn. testcase : testcases/setup.yml output : - session_token - test : name : create user and check result. variables : token : $session_token testcase : testcases/deps/check_and_create.yml","title":"\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b"},{"location":"prepare/testcase-layer/#testsuite","text":"\u5f53\u6d4b\u8bd5\u7528\u4f8b\u6570\u91cf\u6bd4\u8f83\u591a\u4ee5\u540e\uff0c\u4e3a\u4e86\u65b9\u4fbf\u7ba1\u7406\u548c\u5b9e\u73b0\u6279\u91cf\u8fd0\u884c\uff0c\u901a\u5e38\u9700\u8981\u4f7f\u7528\u6d4b\u8bd5\u7528\u4f8b\u96c6\u6765\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u8fdb\u884c\u7ec4\u7ec7\u3002 \u5728\u524d\u6587\u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u6a21\u578b\u4e2d\u4e5f\u5f3a\u8c03\u4e86\uff0c\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u662f\u6d4b\u8bd5\u7528\u4f8b\u7684 \u65e0\u5e8f \u96c6\u5408\uff0c\u96c6\u5408\u4e2d\u7684\u6d4b\u8bd5\u7528\u4f8b\u5e94\u8be5\u90fd\u662f\u76f8\u4e92\u72ec\u7acb\uff0c\u4e0d\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\u7684\uff1b\u5982\u679c\u786e\u5b9e\u5b58\u5728\u5148\u540e\u4f9d\u8d56\u5173\u7cfb\uff0c\u90a3\u5c31\u9700\u8981\u5728\u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b8c\u6210\u4f9d\u8d56\u7684\u5904\u7406\u3002 \u56e0\u4e3a\u662f \u65e0\u5e8f \u96c6\u5408\uff0c\u56e0\u6b64\u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u63cf\u8ff0\u5f62\u5f0f\u4f1a\u4e0e\u6d4b\u8bd5\u7528\u4f8b\u6709\u4e9b\u4e0d\u540c\uff0c\u5728\u6bcf\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u96c6\u6587\u4ef6\u4e2d\uff0c\u7b2c\u4e00\u5c42\u7ea7\u5b58\u5728\u4e24\u7c7b\u5b57\u6bb5\uff1a config: \u6d4b\u8bd5\u7528\u4f8b\u96c6\u7684\u603b\u4f53\u914d\u7f6e\u53c2\u6570 testcases: \u503c\u4e3a\u5b57\u5178\u7ed3\u6784\uff08\u65e0\u5e8f\uff09\uff0ckey \u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u540d\u79f0\uff0cvalue \u4e3a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5185\u5bb9\uff1b\u5728\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\u65f6\u4e5f\u53ef\u4ee5\u6307\u5b9a variables\uff0c\u5b9e\u73b0\u5bf9\u5f15\u7528\u6d4b\u8bd5\u7528\u4f8b\u4e2d variables \u7684\u8986\u76d6\u3002","title":"\u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09"},{"location":"prepare/testcase-layer/#_5","text":"config : name : create users with uid variables : device_sn : ${gen_random_string(15)} var_a : ${gen_random_string(5)} var_b : $var_a base_url : \"http://127.0.0.1:5000\" testcases : create user 1000 and check result. : testcase : testcases/create_user.yml variables : uid : 1000 var_c : ${gen_random_string(5)} var_d : $var_c create user 1001 and check result. : testcase : testcases/create_user.yml variables : uid : 1001 var_c : ${gen_random_string(5)} var_d : $var_c","title":"\u975e\u53c2\u6570\u5316\u573a\u666f"},{"location":"prepare/testcase-layer/#parameters","text":"\u5bf9\u4e8e\u53c2\u6570\u5316\u573a\u666f\uff0c\u53ef\u901a\u8fc7 parameters \u5b9e\u73b0\uff0c\u63cf\u8ff0\u5f62\u5f0f\u5982\u4e0b\u6240\u793a\u3002 config : name : create users with parameters variables : device_sn : ${gen_random_string(15)} base_url : \"http://127.0.0.1:5000\" testcases : create user $uid and check result for $device_sn. : testcase : testcases/create_user.yml variables : uid : 1000 device_sn : TESTSUITE_XXX parameters : uid : [ 101 , 102 , 103 ] device_sn : [ TESTSUITE_X1 , TESTSUITE_X2 ] \u53c2\u6570\u5316\u540e\uff0cparameters \u4e2d\u7684\u53d8\u91cf\u5c06\u91c7\u7528\u7b1b\u5361\u5c14\u79ef\u7ec4\u5408\u5f62\u6210\u53c2\u6570\u5217\u8868\uff0c\u4f9d\u6b21\u8986\u76d6 variables \u4e2d\u7684\u53c2\u6570\uff0c\u9a71\u52a8\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u3002","title":"\u53c2\u6570\u5316\u573a\u666f\uff08parameters\uff09"},{"location":"prepare/testcase-layer/#_6","text":"\u5728\u5bf9\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u8fdb\u884c\u7ec4\u7ec7\u7ba1\u7406\u65f6\uff0c\u5bf9\u4e8e\u6587\u4ef6\u7684\u5b58\u50a8\u4f4d\u7f6e\u5747\u6ca1\u6709\u8981\u6c42\u548c\u9650\u5236\uff0c\u5728\u5f15\u7528\u65f6\u53ea\u9700\u8981\u6307\u5b9a\u5bf9\u5e94\u7684\u6587\u4ef6\u8def\u5f84\u5373\u53ef\u3002\u4f46\u4ece\u7ea6\u5b9a\u5927\u4e8e\u914d\u7f6e\u7684\u89d2\u5ea6\uff0c\u6700\u597d\u662f\u6309\u7167\u63a8\u8350\u7684\u6587\u4ef6\u5939\u540d\u79f0\u8fdb\u884c\u5b58\u50a8\u7ba1\u7406\uff0c\u5e76\u53ef\u901a\u8fc7\u5b50\u76ee\u5f55\u5b9e\u73b0\u9879\u76ee\u6a21\u5757\u5206\u7c7b\u7ba1\u7406\u3002 \u63a8\u8350\u7684\u65b9\u5f0f\u6c47\u603b\u5982\u4e0b\uff1a debugtalk.py \u653e\u7f6e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u5047\u8bbe\u4e3a PRJ_ROOT_DIR .env \u653e\u7f6e\u5728\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\uff0c\u8def\u5f84\u4e3a PRJ_ROOT_DIR/.env \u63a5\u53e3\u5b9a\u4e49\uff08API\uff09\u653e\u7f6e\u5728 PRJ_ROOT_DIR/api/ \u76ee\u5f55\u4e0b \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\u653e\u7f6e\u5728 PRJ_ROOT_DIR/testcases/ \u76ee\u5f55\u4e0b \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\u6587\u4ef6\u5fc5\u987b\u653e\u7f6e\u5728 PRJ_ROOT_DIR/testsuites/ \u76ee\u5f55\u4e0b data \u6587\u4ef6\u5939\uff1a\u5b58\u50a8\u53c2\u6570\u5316\u6587\u4ef6\uff0c\u6216\u8005\u9879\u76ee\u4f9d\u8d56\u7684\u6587\u4ef6\uff0c\u8def\u5f84\u4e3a PRJ_ROOT_DIR/data/ reports \u6587\u4ef6\u5939\uff1a\u5b58\u50a8 \bHTML \u6d4b\u8bd5\u62a5\u544a\uff0c\u751f\u6210\u8def\u5f84\u4e3a PRJ_ROOT_DIR/reports/ \u76ee\u5f55\u7ed3\u6784\u5982\u4e0b\u6240\u793a\uff1a $ tree tests tests \u251c\u2500\u2500 .env \u251c\u2500\u2500 data \u2502 \u251c\u2500\u2500 app_version.csv \u2502 \u2514\u2500\u2500 account.csv \u251c\u2500\u2500 api \u2502 \u251c\u2500\u2500 create_user.yml \u2502 \u251c\u2500\u2500 get_headers.yml \u2502 \u251c\u2500\u2500 get_token.yml \u2502 \u251c\u2500\u2500 get_user.yml \u2502 \u2514\u2500\u2500 reset_all.yml \u251c\u2500\u2500 debugtalk.py \u251c\u2500\u2500 testcases \u2502 \u251c\u2500\u2500 create_user.yml \u2502 \u251c\u2500\u2500 deps \u2502 \u2502 \u2514\u2500\u2500 check_and_create.yml \u2502 \u2514\u2500\u2500 setup.yml \u2514\u2500\u2500 testsuites \u251c\u2500\u2500 create_users.yml \u2514\u2500\u2500 create_users_with_parameters.yml \u9879\u76ee\u811a\u624b\u67b6 \u540c\u65f6\uff0c\u5728 HttpRunner \u4e2d\u5b9e\u73b0\u4e86\u4e00\u4e2a\u811a\u624b\u67b6\u5de5\u5177\uff0c\u53ef\u4ee5\u5feb\u901f\u521b\u5efa\u9879\u76ee\u7684\u76ee\u5f55\u7ed3\u6784\u3002\u8be5\u60f3\u6cd5\u6765\u6e90\u4e8e Django \u7684 django-admin.py startproject project_name \u3002 \u4f7f\u7528\u65b9\u5f0f\u4e5f\u4e0e Django \u7c7b\u4f3c\uff0c\u53ea\u9700\u8981\u901a\u8fc7 --startproject \u6307\u5b9a\u65b0\u9879\u76ee\u7684\u540d\u79f0\u5373\u53ef\u3002 $ hrun --startproject demo Start to create new project: demo CWD: /Users/debugtalk/MyProjects/examples created folder: demo created folder: demo/api created folder: demo/testcases created folder: demo/testsuites created folder: demo/reports created file: demo/debugtalk.py created file: demo/.env","title":"\u6587\u4ef6\u76ee\u5f55\u7ed3\u6784\u7ba1\u7406 && \u811a\u624b\u67b6\u5de5\u5177"},{"location":"prepare/testcase-layer/#_7","text":"\u300aHttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u673a\u5236\uff08\u5df2\u8fc7\u671f\uff09\u300b \u6d4b\u8bd5\u7528\u4f8b\u5206\u5c42\u8be6\u7ec6\u793a\u4f8b\uff1a HttpRunner/tests","title":"\u76f8\u5173\u53c2\u8003"},{"location":"prepare/testcase-structure/","text":"YAML & JSON \u00b6 HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u652f\u6301\u4e24\u79cd\u6587\u4ef6\u683c\u5f0f\uff1aYAML \u548c JSON\u3002 JSON \u548c YAML \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u5b8c\u5168\u7b49\u4ef7\uff0c\u5305\u542b\u7684\u4fe1\u606f\u5185\u5bb9\u4e5f\u5b8c\u5168\u76f8\u540c\u3002 \u5bf9\u4e8e\u65b0\u624b\u6765\u8bf4\uff0c\u63a8\u8350\u4f7f\u7528 JSON \u683c\u5f0f\uff0c\u867d\u7136\u63cf\u8ff0\u5f62\u5f0f\u4e0a\u7a0d\u663e\u7d2f\u8d58\uff0c\u4f46\u662f\u4e0d\u5bb9\u6613\u51fa\u9519\uff08\u5927\u591a\u7f16\u8f91\u5668\u90fd\u5177\u6709 JSON \u683c\u5f0f\u7684\u68c0\u6d4b\u529f\u80fd\uff09\uff1b\u540c\u65f6\uff0cHttpRunner \u4e5f\u5185\u7f6e\u4e86 JSON \u683c\u5f0f\u6b63\u786e\u6027\u68c0\u6d4b\u548c\u6837\u5f0f\u7f8e\u5316\u529f\u80fd\uff0c\u8be6\u60c5\u53ef\u67e5\u770b \u300aValidate & Prettify\u300b \u3002 \u5bf9\u4e8e\u719f\u6089 YAML \u683c\u5f0f\u7684\u4eba\u6765\u8bf4\uff0c\u7f16\u5199\u7ef4\u62a4 YAML \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4f1a\u66f4\u7b80\u6d01\uff0c\u4f46\u524d\u63d0\u662f\u8981\u4fdd\u8bc1 YAML \u683c\u5f0f\u6ca1\u6709\u8bed\u6cd5\u9519\u8bef\u3002 \u5bf9\u4e8e\u4e24\u79cd\u683c\u5f0f\u7684\u5c55\u793a\u5dee\u5f02\uff0c\u53ef\u4ee5\u5bf9\u6bd4\u67e5\u770b demo-quickstart-6.json \u548c demo-quickstart-6.yml \u83b7\u53d6\u521d\u6b65\u7684\u5370\u8c61\u3002 \u540e\u9762\u4e3a\u4e86\u66f4\u6e05\u6670\u7684\u63cf\u8ff0\uff0c\u7edf\u4e00\u91c7\u7528 JSON \u683c\u5f0f\u4f5c\u4e3a\u793a\u4f8b\u3002 \u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784 \u00b6 \u5728 HttpRunner \u4e2d\uff0c\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u4e3b\u8981\u57fa\u4e8e\u4e09\u4e2a\u6982\u5ff5\uff1a \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\uff1a\u5bf9\u5e94\u4e00\u4e2a\u6587\u4ef6\u5939\uff0c\u5305\u542b\u5355\u4e2a\u6216\u591a\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08 YAML/JSON \uff09\u6587\u4ef6 \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\uff1a\u5bf9\u5e94\u4e00\u4e2a YAML/JSON \u6587\u4ef6\uff0c\u5305\u542b\u5355\u4e2a\u6216\u591a\u4e2a\u6d4b\u8bd5\u6b65\u9aa4 \u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\uff1a\u5bf9\u5e94 YAML/JSON \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a test \uff0c\b\u63cf\u8ff0\u5355\u6b21\u63a5\u53e3\u6d4b\u8bd5\u7684\u5168\u90e8\u5185\u5bb9\uff0c\u5305\u62ec\u53d1\u8d77\u63a5\u53e3\u8bf7\u6c42\u3001\u89e3\u6790\u54cd\u5e94\u7ed3\u679c\u3001\u6821\u9a8c\u7ed3\u679c\u7b49 \u5bf9\u4e8e\u5355\u4e2a YAML/JSON \u6587\u4ef6\u6765\u8bf4\uff0c\u6570\u636e\u5b58\u50a8\u7ed3\u6784\u4e3a list of dict \u7684\u5f62\u5f0f\uff0c\u5176\u4e2d\u53ef\u80fd\u5305\u542b\u4e00\u4e2a\u5168\u5c40\u914d\u7f6e\u9879\uff08config\uff09\u548c\u82e5\u5e72\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u3002 \u5177\u4f53\u5730\uff1a config\uff1a\u4f5c\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5168\u5c40\u914d\u7f6e\u9879 test\uff1a\u5bf9\u5e94\u5355\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5b58\u5728\u987a\u5e8f\u5173\u7cfb\uff0c\u8fd0\u884c\u65f6\u5c06\u4ece\u524d\u5f80\u540e\u4f9d\u6b21\u8fd0\u884c\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4 \u5bf9\u5e94\u7684 JSON \u683c\u5f0f\u5982\u4e0b\u6240\u793a\uff1a [ { \"config\" : { ... } }, { \"test\" : { ... } }, { \"test\" : { ... } } ] \u53d8\u91cf\u7a7a\u95f4\uff08context\uff09\u4f5c\u7528\u57df \u00b6 \u5728\u6d4b\u8bd5\u7528\u4f8b\u5185\u90e8\uff0cHttpRunner \u5212\u5206\u4e86\u4e24\u5c42\u53d8\u91cf\u7a7a\u95f4\u4f5c\u7528\u57df\uff08context\uff09\u3002 config\uff1a\u4f5c\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5168\u5c40\u914d\u7f6e\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff1b test\uff1a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u53d8\u91cf\u7a7a\u95f4\uff08context\uff09\u4f1a\u7ee7\u627f\u6216\u8986\u76d6 config \u4e2d\u5b9a\u4e49\u7684\u5185\u5bb9\uff1b \u82e5\u67d0\u53d8\u91cf\u5728 config \u4e2d\u5b9a\u4e49\u4e86\uff0c\u5728\u67d0 test \u4e2d\u6ca1\u6709\u5b9a\u4e49\uff0c\u5219\u8be5 test \u4f1a\u7ee7\u627f\u8be5\u53d8\u91cf \u82e5\u67d0\u53d8\u91cf\u5728 config \u548c\u67d0 test \u4e2d\u90fd\u5b9a\u4e49\u4e86\uff0c\u5219\u8be5 test \u4e2d\u4f7f\u7528\u81ea\u5df1\u5b9a\u4e49\u7684\u53d8\u91cf\u503c \u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u7684\u53d8\u91cf\u7a7a\u95f4\u76f8\u4e92\u72ec\u7acb\uff0c\u4e92\u4e0d\u5f71\u54cd\uff1b \u5982\u9700\u5728\u591a\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u4e2d\u4f20\u9012\u53c2\u6570\u503c\uff0c\u5219\u9700\u8981\u4f7f\u7528 extract \u5173\u952e\u5b57\uff0c\u5e76\u4e14\u53ea\u80fd\u4ece\u524d\u5f80\u540e\u4f20\u9012 config \u00b6 \"config\" : { \"name\" : \"testcase description\" , \"parameters\" : [ { \"user_agent\" : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ]}, { \"app_version\" : \"${P(app_version.csv)}\" }, { \"os_platform\" : \"${get_os_platform()}\" } ], \"variables\" : [ { \"user_agent\" : \"iOS/10.3\" }, { \"device_sn\" : \"${gen_random_string(15)}\" }, { \"os_platform\" : \"ios\" } ], \"request\" : { \"base_url\" : \"http://127.0.0.1:5000\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"device_sn\" : \"$device_sn\" } }, \"output\" : [ \"token\" ] } Key required? format descrption name Yes string \u6d4b\u8bd5\u7528\u4f8b\u7684\u540d\u79f0\uff0c\u5728\u6d4b\u8bd5\u62a5\u544a\u4e2d\u5c06\u4f5c\u4e3a\u6807\u9898 variables No list of dict \u5b9a\u4e49\u7684\u5168\u5c40\u53d8\u91cf\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b parameters No list of dict \u5168\u5c40\u53c2\u6570\uff0c\u7528\u4e8e\u5b9e\u73b0\u6570\u636e\u5316\u9a71\u52a8\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b request No dict request \u7684\u516c\u5171\u53c2\u6570\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b\uff1b\u5e38\u7528\u53c2\u6570\u5305\u62ec base_url \u548c headers request Key required? format descrption base_url No string \u6d4b\u8bd5\u7528\u4f8b\u8bf7\u6c42 URL \u7684\u516c\u5171 host\uff0c\u6307\u5b9a\u8be5\u53c2\u6570\u540e\uff0ctest \u4e2d\u7684 url \u53ef\u4ee5\u53ea\u63cf\u8ff0 path \u90e8\u5206 headers No dict request \u4e2d headers \u7684\u516c\u5171\u53c2\u6570\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b output No list \u6574\u4e2a\u7528\u4f8b\u8f93\u51fa\u7684\u53c2\u6570\u5217\u8868\uff0c\u53ef\u8f93\u51fa\u7684\u53c2\u6570\u5305\u62ec\u516c\u5171\u7684 variable \u548c extract \u7684\u53c2\u6570; \u5728 log-level \u4e3a debug \u6a21\u5f0f\u4e0b\uff0c\u4f1a\u5728 terminal \u4e2d\u6253\u5370\u51fa\u53c2\u6570\u5185\u5bb9 test \u00b6 \"test\" : { \"name\" : \"get token with $user_agent, $os_platform, $app_version\" , \"request\" : { \"url\" : \"/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"app_version\" : \"$app_version\" , \"os_platform\" : \"$os_platform\" , \"user_agent\" : \"$user_agent\" }, \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" }, \"extract\" : [ { \"token\" : \"content.token\" } ], \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]} ], \"setup_hooks\" : [], \"teardown_hooks\" : [] } } Key required? format descrption name Yes string \u6d4b\u8bd5\u6b65\u9aa4\u7684\u540d\u79f0\uff0c\u5728\u6d4b\u8bd5\u62a5\u544a\u4e2d\u5c06\u4f5c\u4e3a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u540d\u79f0 request Yes dict HTTP \u8bf7\u6c42\u7684\u8be6\u7ec6\u5185\u5bb9\uff1b\u53ef\u7528\u53c2\u6570\u8be6\u89c1 python-requests \u5b98\u65b9\u6587\u6863 variables No list of dict \u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\uff0c\u4f5c\u7528\u57df\u4e3a\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4 extract No list \u4ece\u5f53\u524d HTTP \u8bf7\u6c42\u7684\u54cd\u5e94\u7ed3\u679c\u4e2d\u63d0\u53d6\u53c2\u6570\uff0c\u5e76\u4fdd\u5b58\u5230\u53c2\u6570\u53d8\u91cf\u4e2d\uff08\u4f8b\u5982 token \uff09\uff0c\u540e\u7eed\u6d4b\u8bd5\u7528\u4f8b\u53ef\u901a\u8fc7 $token \u7684\u5f62\u5f0f\u8fdb\u884c\u5f15\u7528 validate No list \u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b9a\u4e49\u7684\u7ed3\u679c\u6821\u9a8c\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\uff0c\u7528\u4e8e\u5b9e\u73b0\u5bf9\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u7ed3\u679c\u7684\u6821\u9a8c setup_hooks No list \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u524d\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c teardown_hooks No list \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u540e\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u6237\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c extract \u652f\u6301\u591a\u79cd\u63d0\u53d6\u65b9\u5f0f\uff1a \u54cd\u5e94\u7ed3\u679c\u4e3a JSON \u7ed3\u6784\uff0c\u53ef\u91c7\u7528 . \u8fd0\u7b97\u7b26\u7684\u65b9\u5f0f\uff0c\u4f8b\u5982 headers.Content-Type \u3001 content.success \uff1b \u54cd\u5e94\u7ed3\u679c\u4e3a text/html \u7ed3\u6784\uff0c\u53ef\u91c7\u7528\u6b63\u5219\u8868\u8fbe\u5f0f\u7684\u65b9\u5f0f\uff0c\u4f8b\u5982 blog-motto\\\">(.*) \u8be6\u60c5\u53ef\u9605\u8bfb \u300aApiTestEngine\uff0c\u4e0d\u518d\u5c40\u9650\u4e8eAPI\u7684\u6d4b\u8bd5\u300b validate \u652f\u6301\u4e24\u79cd\u683c\u5f0f\uff1a {\"comparator_name\": [check_item, expect_value]} {\"check\": check_item, \"comparator\": comparator_name, \"expect\": expect_value} hooks setup_hooks \u51fd\u6570\u653e\u7f6e\u4e8e debugtalk.py \u4e2d\uff0c\u5e76\u4e14\u5fc5\u987b\u5305\u542b\u4e09\u4e2a\u53c2\u6570\uff1a method: \u8bf7\u6c42\u65b9\u6cd5\uff0ce.g. GET, POST, PUT url: \u8bf7\u6c42 URL kwargs: request \u7684\u53c2\u6570\u5b57\u5178 teardown_hooks \u51fd\u6570\u653e\u7f6e\u4e8e debugtalk.py \u4e2d\uff0c\u5e76\u4e14\u5fc5\u987b\u5305\u542b\u4e00\u4e2a\u53c2\u6570\uff1a resp_obj: requests.Response \u5b9e\u4f8b \u5173\u4e8e setup_hooks \u548c teardown_hooks \u7684\u66f4\u591a\u5185\u5bb9\uff0c\u8bf7\u53c2\u8003 \u300ahook \u673a\u5236\u300b \u3002","title":"\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7"},{"location":"prepare/testcase-structure/#yaml-json","text":"HttpRunner \u7684\u6d4b\u8bd5\u7528\u4f8b\u652f\u6301\u4e24\u79cd\u6587\u4ef6\u683c\u5f0f\uff1aYAML \u548c JSON\u3002 JSON \u548c YAML \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u5b8c\u5168\u7b49\u4ef7\uff0c\u5305\u542b\u7684\u4fe1\u606f\u5185\u5bb9\u4e5f\u5b8c\u5168\u76f8\u540c\u3002 \u5bf9\u4e8e\u65b0\u624b\u6765\u8bf4\uff0c\u63a8\u8350\u4f7f\u7528 JSON \u683c\u5f0f\uff0c\u867d\u7136\u63cf\u8ff0\u5f62\u5f0f\u4e0a\u7a0d\u663e\u7d2f\u8d58\uff0c\u4f46\u662f\u4e0d\u5bb9\u6613\u51fa\u9519\uff08\u5927\u591a\u7f16\u8f91\u5668\u90fd\u5177\u6709 JSON \u683c\u5f0f\u7684\u68c0\u6d4b\u529f\u80fd\uff09\uff1b\u540c\u65f6\uff0cHttpRunner \u4e5f\u5185\u7f6e\u4e86 JSON \u683c\u5f0f\u6b63\u786e\u6027\u68c0\u6d4b\u548c\u6837\u5f0f\u7f8e\u5316\u529f\u80fd\uff0c\u8be6\u60c5\u53ef\u67e5\u770b \u300aValidate & Prettify\u300b \u3002 \u5bf9\u4e8e\u719f\u6089 YAML \u683c\u5f0f\u7684\u4eba\u6765\u8bf4\uff0c\u7f16\u5199\u7ef4\u62a4 YAML \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4f1a\u66f4\u7b80\u6d01\uff0c\u4f46\u524d\u63d0\u662f\u8981\u4fdd\u8bc1 YAML \u683c\u5f0f\u6ca1\u6709\u8bed\u6cd5\u9519\u8bef\u3002 \u5bf9\u4e8e\u4e24\u79cd\u683c\u5f0f\u7684\u5c55\u793a\u5dee\u5f02\uff0c\u53ef\u4ee5\u5bf9\u6bd4\u67e5\u770b demo-quickstart-6.json \u548c demo-quickstart-6.yml \u83b7\u53d6\u521d\u6b65\u7684\u5370\u8c61\u3002 \u540e\u9762\u4e3a\u4e86\u66f4\u6e05\u6670\u7684\u63cf\u8ff0\uff0c\u7edf\u4e00\u91c7\u7528 JSON \u683c\u5f0f\u4f5c\u4e3a\u793a\u4f8b\u3002","title":"YAML & JSON"},{"location":"prepare/testcase-structure/#_1","text":"\u5728 HttpRunner \u4e2d\uff0c\u6d4b\u8bd5\u7528\u4f8b\u7ec4\u7ec7\u4e3b\u8981\u57fa\u4e8e\u4e09\u4e2a\u6982\u5ff5\uff1a \u6d4b\u8bd5\u7528\u4f8b\u96c6\uff08testsuite\uff09\uff1a\u5bf9\u5e94\u4e00\u4e2a\u6587\u4ef6\u5939\uff0c\u5305\u542b\u5355\u4e2a\u6216\u591a\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff08 YAML/JSON \uff09\u6587\u4ef6 \u6d4b\u8bd5\u7528\u4f8b\uff08testcase\uff09\uff1a\u5bf9\u5e94\u4e00\u4e2a YAML/JSON \u6587\u4ef6\uff0c\u5305\u542b\u5355\u4e2a\u6216\u591a\u4e2a\u6d4b\u8bd5\u6b65\u9aa4 \u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\uff1a\u5bf9\u5e94 YAML/JSON \u6587\u4ef6\u4e2d\u7684\u4e00\u4e2a test \uff0c\b\u63cf\u8ff0\u5355\u6b21\u63a5\u53e3\u6d4b\u8bd5\u7684\u5168\u90e8\u5185\u5bb9\uff0c\u5305\u62ec\u53d1\u8d77\u63a5\u53e3\u8bf7\u6c42\u3001\u89e3\u6790\u54cd\u5e94\u7ed3\u679c\u3001\u6821\u9a8c\u7ed3\u679c\u7b49 \u5bf9\u4e8e\u5355\u4e2a YAML/JSON \u6587\u4ef6\u6765\u8bf4\uff0c\u6570\u636e\u5b58\u50a8\u7ed3\u6784\u4e3a list of dict \u7684\u5f62\u5f0f\uff0c\u5176\u4e2d\u53ef\u80fd\u5305\u542b\u4e00\u4e2a\u5168\u5c40\u914d\u7f6e\u9879\uff08config\uff09\u548c\u82e5\u5e72\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u3002 \u5177\u4f53\u5730\uff1a config\uff1a\u4f5c\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5168\u5c40\u914d\u7f6e\u9879 test\uff1a\u5bf9\u5e94\u5355\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08teststep\uff09\uff0c\u6d4b\u8bd5\u7528\u4f8b\u5b58\u5728\u987a\u5e8f\u5173\u7cfb\uff0c\u8fd0\u884c\u65f6\u5c06\u4ece\u524d\u5f80\u540e\u4f9d\u6b21\u8fd0\u884c\u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4 \u5bf9\u5e94\u7684 JSON \u683c\u5f0f\u5982\u4e0b\u6240\u793a\uff1a [ { \"config\" : { ... } }, { \"test\" : { ... } }, { \"test\" : { ... } } ]","title":"\u6d4b\u8bd5\u7528\u4f8b\u7ed3\u6784"},{"location":"prepare/testcase-structure/#context","text":"\u5728\u6d4b\u8bd5\u7528\u4f8b\u5185\u90e8\uff0cHttpRunner \u5212\u5206\u4e86\u4e24\u5c42\u53d8\u91cf\u7a7a\u95f4\u4f5c\u7528\u57df\uff08context\uff09\u3002 config\uff1a\u4f5c\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u7684\u5168\u5c40\u914d\u7f6e\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff1b test\uff1a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u53d8\u91cf\u7a7a\u95f4\uff08context\uff09\u4f1a\u7ee7\u627f\u6216\u8986\u76d6 config \u4e2d\u5b9a\u4e49\u7684\u5185\u5bb9\uff1b \u82e5\u67d0\u53d8\u91cf\u5728 config \u4e2d\u5b9a\u4e49\u4e86\uff0c\u5728\u67d0 test \u4e2d\u6ca1\u6709\u5b9a\u4e49\uff0c\u5219\u8be5 test \u4f1a\u7ee7\u627f\u8be5\u53d8\u91cf \u82e5\u67d0\u53d8\u91cf\u5728 config \u548c\u67d0 test \u4e2d\u90fd\u5b9a\u4e49\u4e86\uff0c\u5219\u8be5 test \u4e2d\u4f7f\u7528\u81ea\u5df1\u5b9a\u4e49\u7684\u53d8\u91cf\u503c \u5404\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u7684\u53d8\u91cf\u7a7a\u95f4\u76f8\u4e92\u72ec\u7acb\uff0c\u4e92\u4e0d\u5f71\u54cd\uff1b \u5982\u9700\u5728\u591a\u4e2a\u6d4b\u8bd5\u6b65\u9aa4\uff08test\uff09\u4e2d\u4f20\u9012\u53c2\u6570\u503c\uff0c\u5219\u9700\u8981\u4f7f\u7528 extract \u5173\u952e\u5b57\uff0c\u5e76\u4e14\u53ea\u80fd\u4ece\u524d\u5f80\u540e\u4f20\u9012","title":"\u53d8\u91cf\u7a7a\u95f4\uff08context\uff09\u4f5c\u7528\u57df"},{"location":"prepare/testcase-structure/#config","text":"\"config\" : { \"name\" : \"testcase description\" , \"parameters\" : [ { \"user_agent\" : [ \"iOS/10.1\" , \"iOS/10.2\" , \"iOS/10.3\" ]}, { \"app_version\" : \"${P(app_version.csv)}\" }, { \"os_platform\" : \"${get_os_platform()}\" } ], \"variables\" : [ { \"user_agent\" : \"iOS/10.3\" }, { \"device_sn\" : \"${gen_random_string(15)}\" }, { \"os_platform\" : \"ios\" } ], \"request\" : { \"base_url\" : \"http://127.0.0.1:5000\" , \"headers\" : { \"Content-Type\" : \"application/json\" , \"device_sn\" : \"$device_sn\" } }, \"output\" : [ \"token\" ] } Key required? format descrption name Yes string \u6d4b\u8bd5\u7528\u4f8b\u7684\u540d\u79f0\uff0c\u5728\u6d4b\u8bd5\u62a5\u544a\u4e2d\u5c06\u4f5c\u4e3a\u6807\u9898 variables No list of dict \u5b9a\u4e49\u7684\u5168\u5c40\u53d8\u91cf\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b parameters No list of dict \u5168\u5c40\u53c2\u6570\uff0c\u7528\u4e8e\u5b9e\u73b0\u6570\u636e\u5316\u9a71\u52a8\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b request No dict request \u7684\u516c\u5171\u53c2\u6570\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b\uff1b\u5e38\u7528\u53c2\u6570\u5305\u62ec base_url \u548c headers request Key required? format descrption base_url No string \u6d4b\u8bd5\u7528\u4f8b\u8bf7\u6c42 URL \u7684\u516c\u5171 host\uff0c\u6307\u5b9a\u8be5\u53c2\u6570\u540e\uff0ctest \u4e2d\u7684 url \u53ef\u4ee5\u53ea\u63cf\u8ff0 path \u90e8\u5206 headers No dict request \u4e2d headers \u7684\u516c\u5171\u53c2\u6570\uff0c\u4f5c\u7528\u57df\u4e3a\u6574\u4e2a\u7528\u4f8b output No list \u6574\u4e2a\u7528\u4f8b\u8f93\u51fa\u7684\u53c2\u6570\u5217\u8868\uff0c\u53ef\u8f93\u51fa\u7684\u53c2\u6570\u5305\u62ec\u516c\u5171\u7684 variable \u548c extract \u7684\u53c2\u6570; \u5728 log-level \u4e3a debug \u6a21\u5f0f\u4e0b\uff0c\u4f1a\u5728 terminal \u4e2d\u6253\u5370\u51fa\u53c2\u6570\u5185\u5bb9","title":"config"},{"location":"prepare/testcase-structure/#test","text":"\"test\" : { \"name\" : \"get token with $user_agent, $os_platform, $app_version\" , \"request\" : { \"url\" : \"/api/get-token\" , \"method\" : \"POST\" , \"headers\" : { \"app_version\" : \"$app_version\" , \"os_platform\" : \"$os_platform\" , \"user_agent\" : \"$user_agent\" }, \"json\" : { \"sign\" : \"${get_sign($user_agent, $device_sn, $os_platform, $app_version)}\" }, \"extract\" : [ { \"token\" : \"content.token\" } ], \"validate\" : [ { \"eq\" : [ \"status_code\" , 200 ]}, { \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]}, { \"eq\" : [ \"content.success\" , true ]} ], \"setup_hooks\" : [], \"teardown_hooks\" : [] } } Key required? format descrption name Yes string \u6d4b\u8bd5\u6b65\u9aa4\u7684\u540d\u79f0\uff0c\u5728\u6d4b\u8bd5\u62a5\u544a\u4e2d\u5c06\u4f5c\u4e3a\u6d4b\u8bd5\u6b65\u9aa4\u7684\u540d\u79f0 request Yes dict HTTP \u8bf7\u6c42\u7684\u8be6\u7ec6\u5185\u5bb9\uff1b\u53ef\u7528\u53c2\u6570\u8be6\u89c1 python-requests \u5b98\u65b9\u6587\u6863 variables No list of dict \u6d4b\u8bd5\u6b65\u9aa4\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\uff0c\u4f5c\u7528\u57df\u4e3a\u5f53\u524d\u6d4b\u8bd5\u6b65\u9aa4 extract No list \u4ece\u5f53\u524d HTTP \u8bf7\u6c42\u7684\u54cd\u5e94\u7ed3\u679c\u4e2d\u63d0\u53d6\u53c2\u6570\uff0c\u5e76\u4fdd\u5b58\u5230\u53c2\u6570\u53d8\u91cf\u4e2d\uff08\u4f8b\u5982 token \uff09\uff0c\u540e\u7eed\u6d4b\u8bd5\u7528\u4f8b\u53ef\u901a\u8fc7 $token \u7684\u5f62\u5f0f\u8fdb\u884c\u5f15\u7528 validate No list \u6d4b\u8bd5\u7528\u4f8b\u4e2d\u5b9a\u4e49\u7684\u7ed3\u679c\u6821\u9a8c\u9879\uff0c\u4f5c\u7528\u57df\u4e3a\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\uff0c\u7528\u4e8e\u5b9e\u73b0\u5bf9\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u7ed3\u679c\u7684\u6821\u9a8c setup_hooks No list \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u524d\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u4e8e\u51c6\u5907\u5de5\u4f5c teardown_hooks No list \u5728 HTTP \u8bf7\u6c42\u53d1\u9001\u540e\u6267\u884c hook \u51fd\u6570\uff0c\u4e3b\u8981\u7528\u6237\u6d4b\u8bd5\u540e\u7684\u6e05\u7406\u5de5\u4f5c extract \u652f\u6301\u591a\u79cd\u63d0\u53d6\u65b9\u5f0f\uff1a \u54cd\u5e94\u7ed3\u679c\u4e3a JSON \u7ed3\u6784\uff0c\u53ef\u91c7\u7528 . \u8fd0\u7b97\u7b26\u7684\u65b9\u5f0f\uff0c\u4f8b\u5982 headers.Content-Type \u3001 content.success \uff1b \u54cd\u5e94\u7ed3\u679c\u4e3a text/html \u7ed3\u6784\uff0c\u53ef\u91c7\u7528\u6b63\u5219\u8868\u8fbe\u5f0f\u7684\u65b9\u5f0f\uff0c\u4f8b\u5982 blog-motto\\\">(.*) \u8be6\u60c5\u53ef\u9605\u8bfb \u300aApiTestEngine\uff0c\u4e0d\u518d\u5c40\u9650\u4e8eAPI\u7684\u6d4b\u8bd5\u300b validate \u652f\u6301\u4e24\u79cd\u683c\u5f0f\uff1a {\"comparator_name\": [check_item, expect_value]} {\"check\": check_item, \"comparator\": comparator_name, \"expect\": expect_value} hooks setup_hooks \u51fd\u6570\u653e\u7f6e\u4e8e debugtalk.py \u4e2d\uff0c\u5e76\u4e14\u5fc5\u987b\u5305\u542b\u4e09\u4e2a\u53c2\u6570\uff1a method: \u8bf7\u6c42\u65b9\u6cd5\uff0ce.g. GET, POST, PUT url: \u8bf7\u6c42 URL kwargs: request \u7684\u53c2\u6570\u5b57\u5178 teardown_hooks \u51fd\u6570\u653e\u7f6e\u4e8e debugtalk.py \u4e2d\uff0c\u5e76\u4e14\u5fc5\u987b\u5305\u542b\u4e00\u4e2a\u53c2\u6570\uff1a resp_obj: requests.Response \u5b9e\u4f8b \u5173\u4e8e setup_hooks \u548c teardown_hooks \u7684\u66f4\u591a\u5185\u5bb9\uff0c\u8bf7\u53c2\u8003 \u300ahook \u673a\u5236\u300b \u3002","title":"test"},{"location":"prepare/validate-pretty/","text":"HttpRunner \u4ece 1.3.1 \u7248\u672c\u5f00\u59cb\uff0c\u652f\u6301\u5bf9 JSON \u683c\u5f0f\u6d4b\u8bd5\u7528\u4f8b\u7684\u5185\u5bb9\u8fdb\u884c\u683c\u5f0f\u6b63\u786e\u6027\u68c0\u6d4b\u548c\u6837\u5f0f\u7f8e\u5316\u529f\u80fd\u3002 JSON \u683c\u5f0f\u6b63\u786e\u6027\u68c0\u6d4b \u00b6 \u82e5\u9700\u5bf9 JSON \u683c\u5f0f\u7528\u4f8b\u6587\u4ef6\u7684\u5185\u5bb9\u8fdb\u884c\u6b63\u786e\u6027\u68c0\u6d4b\uff0c\u53ef\u4f7f\u7528 --validate \u53c2\u6570\u3002 \u53ef\u6307\u5b9a\u5355\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --validate docs/data/demo-quickstart.json Start to validate JSON file: docs/data/demo-quickstart.json OK \u4e5f\u53ef\u6307\u5b9a\u591a\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --validate docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json Start to validate JSON file: docs/data/demo-quickstart.json OK WARNING Only JSON file format can be validated, skip docs/data/demo-quickstart.yml Start to validate JSON file: docs/data/demo-quickstart-0.json OK \u5982\u4e0a\u6240\u793a\uff0c\u5f53\u4f20\u5165\u7684\u6587\u4ef6\u540e\u7f00\u4e0d\u662f .json \uff0cHttpRunner \u4f1a\u6253\u5370 WARNING \u4fe1\u606f\uff0c\u5e76\u8df3\u8fc7\u68c0\u6d4b\u3002 \u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u6b63\u786e\uff0c\u5219\u6253\u5370 OK\u3002 \u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u5b58\u5728\u5f02\u5e38\uff0c\u5219\u6253\u5370\u8be6\u7ec6\u7684\u62a5\u9519\u4fe1\u606f\uff0c\u7cbe\u786e\u5230\u9519\u8bef\u5728\u6587\u4ef6\u4e2d\u51fa\u73b0\u7684\u884c\u548c\u5217\u3002 $ hrun --validate docs/data/demo-quickstart.json Start to validate JSON file: docs/data/demo-quickstart.json Expecting ',' delimiter: line 5 column 13 ( char 82 ) JSON \u683c\u5f0f\u7f8e\u5316 \u00b6 \u4e0e YAML \u683c\u5f0f\u4e0d\u540c\uff0cJSON \u683c\u5f0f\u4e0d\u5f3a\u5236\u8981\u6c42\u7f29\u8fdb\u548c\u6362\u884c\uff0c\u8fd9\u6709\u70b9\u7c7b\u4f3c\u4e8e C \u8bed\u8a00\u548c Python \u8bed\u8a00\u7684\u5dee\u5f02\u3002 \u4f8b\u5982\uff0c demo-quickstart.json \u6587\u4ef6\u4e5f\u53ef\u4ee5\u6539\u5199\u4e3a\u5982\u4e0b\u5f62\u5f0f\u3002 [{ \"config\" : { \"name\" : \"testcase description\" , \"variables\" : [], \"request\" : { \"base_url\" : \"\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" }}}},{ \"test\" : { \"name\" : \"/api/get-token\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"user_agent\" : \"iOS/10.3\" , \"os_platform\" : \"ios\" , \"app_version\" : \"2.8.6\" , \"Content-Type\" : \"application/json\" }, \"method\" : \"POST\" , \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" }}, \"validate\" : [{ \"eq\" : [ \"status_code\" , 200 ]},{ \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]},{ \"eq\" : [ \"content.success\" , true ]},{ \"eq\" : [ \"content.token\" , \"baNLX1zhFYP11Seb\" ]}]}},{ \"test\" : { \"name\" : \"/api/users/1000\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"baNLX1zhFYP11Seb\" , \"Content-Type\" : \"application/json\" }, \"method\" : \"POST\" , \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }}, \"validate\" : [{ \"eq\" : [ \"status_code\" , 201 ]},{ \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]},{ \"eq\" : [ \"content.success\" , true ]},{ \"eq\" : [ \"content.msg\" , \"user created successfully.\" ]}]}}] \u867d\u7136\u4e0a\u9762 JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u80fd\u6b63\u5e38\u6267\u884c\uff0c\u4f46\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u53ef\u8bfb\u6027\u592a\u5dee\uff0c\u4e0d\u5229\u4e8e\u9605\u8bfb\u548c\u7ef4\u62a4\u3002 \u9488\u5bf9\u8be5\u9700\u6c42\uff0c\u53ef\u4f7f\u7528 --prettify \u53c2\u6570\u5bf9 JSON \u683c\u5f0f\u7528\u4f8b\u6587\u4ef6\u8fdb\u884c\u6837\u5f0f\u7f8e\u5316\u3002 \u53ef\u6307\u5b9a\u5355\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --prettify docs/data/demo-quickstart.json Start to prettify JSON file: docs/data/demo-quickstart.json success: docs/data/demo-quickstart.pretty.json \u4e5f\u53ef\u6307\u5b9a\u591a\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --prettify docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json WARNING Only JSON file format can be prettified, skip: docs/data/demo-quickstart.yml Start to prettify JSON file: docs/data/demo-quickstart.json success: docs/data/demo-quickstart.pretty.json Start to prettify JSON file: docs/data/demo-quickstart-0.json success: docs/data/demo-quickstart-0.pretty.json \u5982\u4e0a\u6240\u793a\uff0c\u5f53\u4f20\u5165\u7684\u6587\u4ef6\u540e\u7f00\u4e0d\u662f .json \uff0cHttpRunner \u4f1a\u6253\u5370 WARNING \u4fe1\u606f\uff0c\u5e76\u8df3\u8fc7\u68c0\u6d4b\u3002 \u82e5\u8f6c\u6362\u6210\u529f\uff0c\u5219\u6253\u5370\u7f8e\u5316\u540e\u7684\u6587\u4ef6\u8def\u5f84\uff1b\u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u5b58\u5728\u5f02\u5e38\uff0c\u5219\u6253\u5370\u8be6\u7ec6\u7684\u62a5\u9519\u4fe1\u606f\uff0c\u7cbe\u786e\u5230\u9519\u8bef\u5728\u6587\u4ef6\u4e2d\u51fa\u73b0\u7684\u884c\u548c\u5217\u3002","title":"Validate & Prettify"},{"location":"prepare/validate-pretty/#json","text":"\u82e5\u9700\u5bf9 JSON \u683c\u5f0f\u7528\u4f8b\u6587\u4ef6\u7684\u5185\u5bb9\u8fdb\u884c\u6b63\u786e\u6027\u68c0\u6d4b\uff0c\u53ef\u4f7f\u7528 --validate \u53c2\u6570\u3002 \u53ef\u6307\u5b9a\u5355\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --validate docs/data/demo-quickstart.json Start to validate JSON file: docs/data/demo-quickstart.json OK \u4e5f\u53ef\u6307\u5b9a\u591a\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --validate docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json Start to validate JSON file: docs/data/demo-quickstart.json OK WARNING Only JSON file format can be validated, skip docs/data/demo-quickstart.yml Start to validate JSON file: docs/data/demo-quickstart-0.json OK \u5982\u4e0a\u6240\u793a\uff0c\u5f53\u4f20\u5165\u7684\u6587\u4ef6\u540e\u7f00\u4e0d\u662f .json \uff0cHttpRunner \u4f1a\u6253\u5370 WARNING \u4fe1\u606f\uff0c\u5e76\u8df3\u8fc7\u68c0\u6d4b\u3002 \u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u6b63\u786e\uff0c\u5219\u6253\u5370 OK\u3002 \u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u5b58\u5728\u5f02\u5e38\uff0c\u5219\u6253\u5370\u8be6\u7ec6\u7684\u62a5\u9519\u4fe1\u606f\uff0c\u7cbe\u786e\u5230\u9519\u8bef\u5728\u6587\u4ef6\u4e2d\u51fa\u73b0\u7684\u884c\u548c\u5217\u3002 $ hrun --validate docs/data/demo-quickstart.json Start to validate JSON file: docs/data/demo-quickstart.json Expecting ',' delimiter: line 5 column 13 ( char 82 )","title":"JSON \u683c\u5f0f\u6b63\u786e\u6027\u68c0\u6d4b"},{"location":"prepare/validate-pretty/#json_1","text":"\u4e0e YAML \u683c\u5f0f\u4e0d\u540c\uff0cJSON \u683c\u5f0f\u4e0d\u5f3a\u5236\u8981\u6c42\u7f29\u8fdb\u548c\u6362\u884c\uff0c\u8fd9\u6709\u70b9\u7c7b\u4f3c\u4e8e C \u8bed\u8a00\u548c Python \u8bed\u8a00\u7684\u5dee\u5f02\u3002 \u4f8b\u5982\uff0c demo-quickstart.json \u6587\u4ef6\u4e5f\u53ef\u4ee5\u6539\u5199\u4e3a\u5982\u4e0b\u5f62\u5f0f\u3002 [{ \"config\" : { \"name\" : \"testcase description\" , \"variables\" : [], \"request\" : { \"base_url\" : \"\" , \"headers\" : { \"User-Agent\" : \"python-requests/2.18.4\" }}}},{ \"test\" : { \"name\" : \"/api/get-token\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/get-token\" , \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"user_agent\" : \"iOS/10.3\" , \"os_platform\" : \"ios\" , \"app_version\" : \"2.8.6\" , \"Content-Type\" : \"application/json\" }, \"method\" : \"POST\" , \"json\" : { \"sign\" : \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\" }}, \"validate\" : [{ \"eq\" : [ \"status_code\" , 200 ]},{ \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]},{ \"eq\" : [ \"content.success\" , true ]},{ \"eq\" : [ \"content.token\" , \"baNLX1zhFYP11Seb\" ]}]}},{ \"test\" : { \"name\" : \"/api/users/1000\" , \"request\" : { \"url\" : \"http://127.0.0.1:5000/api/users/1000\" , \"headers\" : { \"device_sn\" : \"FwgRiO7CNA50DSU\" , \"token\" : \"baNLX1zhFYP11Seb\" , \"Content-Type\" : \"application/json\" }, \"method\" : \"POST\" , \"json\" : { \"name\" : \"user1\" , \"password\" : \"123456\" }}, \"validate\" : [{ \"eq\" : [ \"status_code\" , 201 ]},{ \"eq\" : [ \"headers.Content-Type\" , \"application/json\" ]},{ \"eq\" : [ \"content.success\" , true ]},{ \"eq\" : [ \"content.msg\" , \"user created successfully.\" ]}]}}] \u867d\u7136\u4e0a\u9762 JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u80fd\u6b63\u5e38\u6267\u884c\uff0c\u4f46\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u53ef\u8bfb\u6027\u592a\u5dee\uff0c\u4e0d\u5229\u4e8e\u9605\u8bfb\u548c\u7ef4\u62a4\u3002 \u9488\u5bf9\u8be5\u9700\u6c42\uff0c\u53ef\u4f7f\u7528 --prettify \u53c2\u6570\u5bf9 JSON \u683c\u5f0f\u7528\u4f8b\u6587\u4ef6\u8fdb\u884c\u6837\u5f0f\u7f8e\u5316\u3002 \u53ef\u6307\u5b9a\u5355\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --prettify docs/data/demo-quickstart.json Start to prettify JSON file: docs/data/demo-quickstart.json success: docs/data/demo-quickstart.pretty.json \u4e5f\u53ef\u6307\u5b9a\u591a\u4e2a JSON \u7528\u4f8b\u6587\u4ef6\u8def\u5f84\u3002 $ hrun --prettify docs/data/demo-quickstart.json docs/data/demo-quickstart.yml docs/data/demo-quickstart-0.json WARNING Only JSON file format can be prettified, skip: docs/data/demo-quickstart.yml Start to prettify JSON file: docs/data/demo-quickstart.json success: docs/data/demo-quickstart.pretty.json Start to prettify JSON file: docs/data/demo-quickstart-0.json success: docs/data/demo-quickstart-0.pretty.json \u5982\u4e0a\u6240\u793a\uff0c\u5f53\u4f20\u5165\u7684\u6587\u4ef6\u540e\u7f00\u4e0d\u662f .json \uff0cHttpRunner \u4f1a\u6253\u5370 WARNING \u4fe1\u606f\uff0c\u5e76\u8df3\u8fc7\u68c0\u6d4b\u3002 \u82e5\u8f6c\u6362\u6210\u529f\uff0c\u5219\u6253\u5370\u7f8e\u5316\u540e\u7684\u6587\u4ef6\u8def\u5f84\uff1b\u82e5 JSON \u6587\u4ef6\u683c\u5f0f\u5b58\u5728\u5f02\u5e38\uff0c\u5219\u6253\u5370\u8be6\u7ec6\u7684\u62a5\u9519\u4fe1\u606f\uff0c\u7cbe\u786e\u5230\u9519\u8bef\u5728\u6587\u4ef6\u4e2d\u51fa\u73b0\u7684\u884c\u548c\u5217\u3002","title":"JSON \u683c\u5f0f\u7f8e\u5316"},{"location":"run-tests/cli/","text":"HttpRunner \u5728\u547d\u4ee4\u884c\u4e2d\u542f\u52a8\u6d4b\u8bd5\u65f6\uff0c\u901a\u8fc7\u6307\u5b9a\u53c2\u6570\uff0c\u53ef\u5b9e\u73b0\u4e30\u5bcc\u7684\u6d4b\u8bd5\u7279\u6027\u63a7\u5236\u3002 $ hrun -h usage: hrun [-h] [-V] [--log-level LOG_LEVEL] [--log-file LOG_FILE] [--dot-env-path DOT_ENV_PATH] [--report-template REPORT_TEMPLATE] [--report-dir REPORT_DIR] [--failfast] [--save-tests] [--startproject STARTPROJECT] [--validate [VALIDATE [VALIDATE ...]]] [--prettify [PRETTIFY [PRETTIFY ...]]] [testcase_paths [testcase_paths ...]] One-stop solution for HTTP(S) testing. positional arguments: testcase_paths testcase file path optional arguments: -h, --help show this help message and exit -V, --version show version --log-level LOG_LEVEL Specify logging level, default is INFO. --log-file LOG_FILE Write logs to specified file path. --dot-env-path DOT_ENV_PATH Specify .env file path, which is useful for keeping sensitive data. --report-template REPORT_TEMPLATE specify report template path. --report-dir REPORT_DIR specify report save directory. --failfast Stop the test run on the first error or failure. --save-tests Save loaded tests and parsed tests to JSON file. --startproject STARTPROJECT Specify new project name. --validate [VALIDATE [VALIDATE ...]] Validate JSON testcase format. --prettify [PRETTIFY [PRETTIFY ...]] Prettify JSON testcase format. \u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u8def\u5f84 \u00b6 \u4f7f\u7528 HttpRunner \u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u8def\u5f84\u65f6\uff0c\u652f\u6301\u591a\u79cd\u65b9\u5f0f\u3002 \u4f7f\u7528 hrun \u547d\u4ee4\u5916\u52a0\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\uff0c\u8fd0\u884c\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u751f\u6210\u4e00\u4e2a\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\uff1a $ hrun filepath/testcase.yml \u5c06\u591a\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u653e\u7f6e\u5230\u6587\u4ef6\u5939\u4e2d\uff0c\u6307\u5b9a\u6587\u4ef6\u5939\u8def\u5f84\u53ef\u5c06\u6587\u4ef6\u5939\u4e0b\u6240\u6709\u6d4b\u8bd5\u7528\u4f8b\u4f5c\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u96c6\u8fdb\u884c\u8fd0\u884c\uff0c\u5e76\u751f\u6210\u4e00\u4e2a\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\uff1a $ hrun testcases_folder_path failfast \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cHttpRunner \u4f1a\u8fd0\u884c\u6307\u5b9a\u7528\u4f8b\u96c6\u4e2d\u7684\u6240\u6709\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u7edf\u8ba1\u6d4b\u8bd5\u7ed3\u679c\u3002 \u5bf9\u4e8e\u67d0\u4e9b\u4f9d\u8d56\u4e8e\u6267\u884c\u987a\u5e8f\u7684\u6d4b\u8bd5\u7528\u4f8b\uff0c\u4f8b\u5982\u9700\u8981\u5148\u767b\u5f55\u6210\u529f\u624d\u80fd\u6267\u884c\u540e\u7eed\u63a5\u53e3\u8bf7\u6c42\u7684\u573a\u666f\uff0c\u5f53\u524d\u9762\u7684\u6d4b\u8bd5\u7528\u4f8b\u6267\u884c\u5931\u8d25\u540e\uff0c\u540e\u7eed\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u90fd\u5fc5\u5c06\u5931\u8d25\uff0c\u56e0\u6b64\u6ca1\u6709\u7ee7\u7eed\u6267\u884c\u7684\u5fc5\u8981\u4e86\u3002 \u82e5\u5e0c\u671b\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u8fc7\u7a0b\u4e2d\uff0c\u9047\u5230\u5931\u8d25\u65f6\u4e0d\u518d\u7ee7\u7eed\u8fd0\u884c\u540e\u7eed\u7528\u4f8b\uff0c\u5219\u53ef\u901a\u8fc7\u5728\u547d\u4ee4\u4e2d\u6dfb\u52a0 --failfast \u5b9e\u73b0\u3002 $ hrun filepath/testcase.yml --failfast \u65e5\u5fd7\u7ea7\u522b \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cHttpRunner \u8fd0\u884c\u65f6\u7684\u65e5\u5fd7\u7ea7\u522b\u4e3a INFO \uff0c\u53ea\u4f1a\u5305\u542b\u6700\u57fa\u672c\u7684\u4fe1\u606f\uff0c\u5305\u62ec\u7528\u4f8b\u540d\u79f0\u3001\u8bf7\u6c42\u7684URL\u548cMethod\u3001\u54cd\u5e94\u7ed3\u679c\u7684\u72b6\u6001\u7801\u3001\u8017\u65f6\u548c\u5185\u5bb9\u5927\u5c0f\u3002 $ hrun docs/data/demo-quickstart-6.json INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 9.08 ms, response_length: 46 bytes . /api/users/1548560655759 INFO POST http://127.0.0.1:5000/api/users/1548560655759 INFO status_code: 201, response_time(ms): 2.89 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.019s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560655.html \u82e5\u9700\u8981\u67e5\u770b\u5230\u66f4\u8be6\u5c3d\u7684\u4fe1\u606f\uff0c\u4f8b\u5982\u8bf7\u6c42\u7684\u53c2\u6570\u548c\u54cd\u5e94\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u53ef\u4ee5\u5c06\u65e5\u5fd7\u7ea7\u522b\u8bbe\u7f6e\u4e3a DEBUG \uff0c\u5373\u5728\u547d\u4ee4\u4e2d\u6dfb\u52a0 --log-level debug \u3002 $ hrun docs/data/demo-quickstart-6.json --log-level debug INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token DEBUG request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True} DEBUG processed request: > POST http://127.0.0.1:5000/api/get-token > kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True, 'timeout': 120} DEBUG ================== request details ================== url : 'http://127.0.0.1:5000/api/get-token' method : 'POST' headers : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json', 'Content-Length': '52'} body : b'{\"sign\": \"2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff\"}' DEBUG ================== response details ================== ok : True url : 'http://127.0.0.1:5000/api/get-token' status_code : 200 reason : 'OK' cookies : {} encoding : None headers : {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'} content_type : 'application/json' json : {'success': True, 'token': 'o6uakmubLrCbpRRS'} INFO status_code: 200, response_time(ms): 9.28 ms, response_length: 46 bytes DEBUG start to extract from response object. DEBUG extract: content.token => o6uakmubLrCbpRRS DEBUG start to validate. DEBUG extract: status_code => 200 DEBUG validate: status_code equals 200(int) ==> pass DEBUG extract: headers.Content-Type => application/json DEBUG validate: headers.Content-Type equals application/json(str) ==> pass DEBUG extract: content.success => True DEBUG validate: content.success equals True(bool) ==> pass . /api/users/1548560716736 INFO POST http://127.0.0.1:5000/api/users/1548560716736 DEBUG request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True} DEBUG processed request: > POST http://127.0.0.1:5000/api/users/1548560716736 > kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True, 'timeout': 120} DEBUG ================== request details ================== url : 'http://127.0.0.1:5000/api/users/1548560716736' method : 'POST' headers : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json', 'Content-Length': '39'} body : b'{\"name\": \"user1\", \"password\": \"123456\"}' DEBUG ================== response details ================== ok : True url : 'http://127.0.0.1:5000/api/users/1548560716736' status_code : 201 reason : 'CREATED' cookies : {} encoding : None headers : {'Content-Type': 'application/json', 'Content-Length': '54', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'} content_type : 'application/json' json : {'success': True, 'msg': 'user created successfully.'} INFO status_code: 201, response_time(ms): 2.77 ms, response_length: 54 bytes DEBUG start to validate. DEBUG extract: status_code => 201 DEBUG validate: status_code equals 201(int) ==> pass DEBUG extract: headers.Content-Type => application/json DEBUG validate: headers.Content-Type equals application/json(str) ==> pass DEBUG extract: content.success => True DEBUG validate: content.success equals True(bool) ==> pass DEBUG extract: content.msg => user created successfully. DEBUG validate: content.msg equals user created successfully.(str) ==> pass . ---------------------------------------------------------------------- Ran 2 tests in 0.022s OK DEBUG No html report template specified, use default. INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560716.html \u4fdd\u5b58\u8be6\u7ec6\u8fc7\u7a0b\u6570\u636e \u00b6 \u4e3a\u4e86\u65b9\u4fbf\u5b9a\u4f4d\u95ee\u9898\uff0c\u8fd0\u884c\u6d4b\u8bd5\u65f6\u53ef\u6307\u5b9a --save-tests \u53c2\u6570\uff0c\u5373\u53ef\u5c06\u8fd0\u884c\u8fc7\u7a0b\u7684\u4e2d\u95f4\u6570\u636e\u4fdd\u5b58\u4e3a\u65e5\u5fd7\u6587\u4ef6\u3002 $ hrun docs/data/demo-quickstart-6.json --save-tests dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.loaded.json dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.parsed.json INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 11.42 ms, response_length: 46 bytes . /api/users/1548560768589 INFO POST http://127.0.0.1:5000/api/users/1548560768589 INFO status_code: 201, response_time(ms): 2.8 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.028s OK dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.summary.json INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560768.html \u65e5\u5fd7\u6587\u4ef6\u5c06\u4fdd\u5b58\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 logs \u6587\u4ef6\u5939\u4e2d\uff0c\u751f\u6210\u7684\u6587\u4ef6\u6709\u5982\u4e0b\u4e09\u4e2a\uff08XXX\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u540d\u79f0\uff09\uff1a XXX.loaded.json \uff1a\u6d4b\u8bd5\u7528\u4f8b\u52a0\u8f7d\u540e\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u52a0\u8f7d\u5305\u62ec\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\uff08YAML/JSON\uff09\u3001debugtalk.py\u3001.env \u7b49\u6240\u6709\u9879\u76ee\u6587\u4ef6\uff0c\u4f8b\u5982 demo-quickstart-6.loaded.json XXX.parsed.json \uff1a\u6d4b\u8bd5\u7528\u4f8b\u89e3\u6790\u540e\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u89e3\u6790\u5185\u5bb9\u5305\u62ec\u6d4b\u8bd5\u7528\u4f8b\u5f15\u7528\uff08API/testcase\uff09\u3001\u53d8\u91cf\u8ba1\u7b97\u548c\u66ff\u6362\u3001base_url \u62fc\u63a5\u7b49\uff0c\u4f8b\u5982 demo-quickstart-6.parsed.json XXX.summary.json \uff1a\u6d4b\u8bd5\u62a5\u544a\u751f\u6210\u524d\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u4f8b\u5982 demo-quickstart-6.summary.json","title":"\u8fd0\u884c\u6d4b\u8bd5(CLI)"},{"location":"run-tests/cli/#_1","text":"\u4f7f\u7528 HttpRunner \u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u8def\u5f84\u65f6\uff0c\u652f\u6301\u591a\u79cd\u65b9\u5f0f\u3002 \u4f7f\u7528 hrun \u547d\u4ee4\u5916\u52a0\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u7684\u8def\u5f84\uff0c\u8fd0\u884c\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u751f\u6210\u4e00\u4e2a\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\uff1a $ hrun filepath/testcase.yml \u5c06\u591a\u4e2a\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u653e\u7f6e\u5230\u6587\u4ef6\u5939\u4e2d\uff0c\u6307\u5b9a\u6587\u4ef6\u5939\u8def\u5f84\u53ef\u5c06\u6587\u4ef6\u5939\u4e0b\u6240\u6709\u6d4b\u8bd5\u7528\u4f8b\u4f5c\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u96c6\u8fdb\u884c\u8fd0\u884c\uff0c\u5e76\u751f\u6210\u4e00\u4e2a\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\uff1a $ hrun testcases_folder_path","title":"\u6307\u5b9a\u6d4b\u8bd5\u7528\u4f8b\u8def\u5f84"},{"location":"run-tests/cli/#failfast","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cHttpRunner \u4f1a\u8fd0\u884c\u6307\u5b9a\u7528\u4f8b\u96c6\u4e2d\u7684\u6240\u6709\u6d4b\u8bd5\u7528\u4f8b\uff0c\u5e76\u7edf\u8ba1\u6d4b\u8bd5\u7ed3\u679c\u3002 \u5bf9\u4e8e\u67d0\u4e9b\u4f9d\u8d56\u4e8e\u6267\u884c\u987a\u5e8f\u7684\u6d4b\u8bd5\u7528\u4f8b\uff0c\u4f8b\u5982\u9700\u8981\u5148\u767b\u5f55\u6210\u529f\u624d\u80fd\u6267\u884c\u540e\u7eed\u63a5\u53e3\u8bf7\u6c42\u7684\u573a\u666f\uff0c\u5f53\u524d\u9762\u7684\u6d4b\u8bd5\u7528\u4f8b\u6267\u884c\u5931\u8d25\u540e\uff0c\u540e\u7eed\u7684\u6d4b\u8bd5\u7528\u4f8b\u4e5f\u90fd\u5fc5\u5c06\u5931\u8d25\uff0c\u56e0\u6b64\u6ca1\u6709\u7ee7\u7eed\u6267\u884c\u7684\u5fc5\u8981\u4e86\u3002 \u82e5\u5e0c\u671b\u6d4b\u8bd5\u7528\u4f8b\u5728\u8fd0\u884c\u8fc7\u7a0b\u4e2d\uff0c\u9047\u5230\u5931\u8d25\u65f6\u4e0d\u518d\u7ee7\u7eed\u8fd0\u884c\u540e\u7eed\u7528\u4f8b\uff0c\u5219\u53ef\u901a\u8fc7\u5728\u547d\u4ee4\u4e2d\u6dfb\u52a0 --failfast \u5b9e\u73b0\u3002 $ hrun filepath/testcase.yml --failfast","title":"failfast"},{"location":"run-tests/cli/#_2","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cHttpRunner \u8fd0\u884c\u65f6\u7684\u65e5\u5fd7\u7ea7\u522b\u4e3a INFO \uff0c\u53ea\u4f1a\u5305\u542b\u6700\u57fa\u672c\u7684\u4fe1\u606f\uff0c\u5305\u62ec\u7528\u4f8b\u540d\u79f0\u3001\u8bf7\u6c42\u7684URL\u548cMethod\u3001\u54cd\u5e94\u7ed3\u679c\u7684\u72b6\u6001\u7801\u3001\u8017\u65f6\u548c\u5185\u5bb9\u5927\u5c0f\u3002 $ hrun docs/data/demo-quickstart-6.json INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 9.08 ms, response_length: 46 bytes . /api/users/1548560655759 INFO POST http://127.0.0.1:5000/api/users/1548560655759 INFO status_code: 201, response_time(ms): 2.89 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.019s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560655.html \u82e5\u9700\u8981\u67e5\u770b\u5230\u66f4\u8be6\u5c3d\u7684\u4fe1\u606f\uff0c\u4f8b\u5982\u8bf7\u6c42\u7684\u53c2\u6570\u548c\u54cd\u5e94\u7684\u8be6\u7ec6\u5185\u5bb9\uff0c\u53ef\u4ee5\u5c06\u65e5\u5fd7\u7ea7\u522b\u8bbe\u7f6e\u4e3a DEBUG \uff0c\u5373\u5728\u547d\u4ee4\u4e2d\u6dfb\u52a0 --log-level debug \u3002 $ hrun docs/data/demo-quickstart-6.json --log-level debug INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token DEBUG request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True} DEBUG processed request: > POST http://127.0.0.1:5000/api/get-token > kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json'}, 'json': {'sign': '2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff'}, 'verify': True, 'timeout': 120} DEBUG ================== request details ================== url : 'http://127.0.0.1:5000/api/get-token' method : 'POST' headers : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'os_platform': 'ios', 'app_version': '2.8.6', 'Content-Type': 'application/json', 'Content-Length': '52'} body : b'{\"sign\": \"2e7c3b5d560a1c8a859edcb9c8b0d3f8349abeff\"}' DEBUG ================== response details ================== ok : True url : 'http://127.0.0.1:5000/api/get-token' status_code : 200 reason : 'OK' cookies : {} encoding : None headers : {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'} content_type : 'application/json' json : {'success': True, 'token': 'o6uakmubLrCbpRRS'} INFO status_code: 200, response_time(ms): 9.28 ms, response_length: 46 bytes DEBUG start to extract from response object. DEBUG extract: content.token => o6uakmubLrCbpRRS DEBUG start to validate. DEBUG extract: status_code => 200 DEBUG validate: status_code equals 200(int) ==> pass DEBUG extract: headers.Content-Type => application/json DEBUG validate: headers.Content-Type equals application/json(str) ==> pass DEBUG extract: content.success => True DEBUG validate: content.success equals True(bool) ==> pass . /api/users/1548560716736 INFO POST http://127.0.0.1:5000/api/users/1548560716736 DEBUG request kwargs(raw): {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True} DEBUG processed request: > POST http://127.0.0.1:5000/api/users/1548560716736 > kwargs: {'headers': {'User-Agent': 'python-requests/2.18.4', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json'}, 'json': {'name': 'user1', 'password': '123456'}, 'verify': True, 'timeout': 120} DEBUG ================== request details ================== url : 'http://127.0.0.1:5000/api/users/1548560716736' method : 'POST' headers : {'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'device_sn': 'W5ACRDytKRQJPhC', 'token': 'o6uakmubLrCbpRRS', 'Content-Type': 'application/json', 'Content-Length': '39'} body : b'{\"name\": \"user1\", \"password\": \"123456\"}' DEBUG ================== response details ================== ok : True url : 'http://127.0.0.1:5000/api/users/1548560716736' status_code : 201 reason : 'CREATED' cookies : {} encoding : None headers : {'Content-Type': 'application/json', 'Content-Length': '54', 'Server': 'Werkzeug/0.14.1 Python/3.6.5+', 'Date': 'Sun, 27 Jan 2019 03:45:16 GMT'} content_type : 'application/json' json : {'success': True, 'msg': 'user created successfully.'} INFO status_code: 201, response_time(ms): 2.77 ms, response_length: 54 bytes DEBUG start to validate. DEBUG extract: status_code => 201 DEBUG validate: status_code equals 201(int) ==> pass DEBUG extract: headers.Content-Type => application/json DEBUG validate: headers.Content-Type equals application/json(str) ==> pass DEBUG extract: content.success => True DEBUG validate: content.success equals True(bool) ==> pass DEBUG extract: content.msg => user created successfully. DEBUG validate: content.msg equals user created successfully.(str) ==> pass . ---------------------------------------------------------------------- Ran 2 tests in 0.022s OK DEBUG No html report template specified, use default. INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560716.html","title":"\u65e5\u5fd7\u7ea7\u522b"},{"location":"run-tests/cli/#_3","text":"\u4e3a\u4e86\u65b9\u4fbf\u5b9a\u4f4d\u95ee\u9898\uff0c\u8fd0\u884c\u6d4b\u8bd5\u65f6\u53ef\u6307\u5b9a --save-tests \u53c2\u6570\uff0c\u5373\u53ef\u5c06\u8fd0\u884c\u8fc7\u7a0b\u7684\u4e2d\u95f4\u6570\u636e\u4fdd\u5b58\u4e3a\u65e5\u5fd7\u6587\u4ef6\u3002 $ hrun docs/data/demo-quickstart-6.json --save-tests dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.loaded.json dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.parsed.json INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200, response_time(ms): 11.42 ms, response_length: 46 bytes . /api/users/1548560768589 INFO POST http://127.0.0.1:5000/api/users/1548560768589 INFO status_code: 201, response_time(ms): 2.8 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0.028s OK dump file: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/docs/data/logs/demo-quickstart-6.summary.json INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548560768.html \u65e5\u5fd7\u6587\u4ef6\u5c06\u4fdd\u5b58\u5728\u9879\u76ee\u6839\u76ee\u5f55\u7684 logs \u6587\u4ef6\u5939\u4e2d\uff0c\u751f\u6210\u7684\u6587\u4ef6\u6709\u5982\u4e0b\u4e09\u4e2a\uff08XXX\u4e3a\u6d4b\u8bd5\u7528\u4f8b\u540d\u79f0\uff09\uff1a XXX.loaded.json \uff1a\u6d4b\u8bd5\u7528\u4f8b\u52a0\u8f7d\u540e\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u52a0\u8f7d\u5305\u62ec\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\uff08YAML/JSON\uff09\u3001debugtalk.py\u3001.env \u7b49\u6240\u6709\u9879\u76ee\u6587\u4ef6\uff0c\u4f8b\u5982 demo-quickstart-6.loaded.json XXX.parsed.json \uff1a\u6d4b\u8bd5\u7528\u4f8b\u89e3\u6790\u540e\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u89e3\u6790\u5185\u5bb9\u5305\u62ec\u6d4b\u8bd5\u7528\u4f8b\u5f15\u7528\uff08API/testcase\uff09\u3001\u53d8\u91cf\u8ba1\u7b97\u548c\u66ff\u6362\u3001base_url \u62fc\u63a5\u7b49\uff0c\u4f8b\u5982 demo-quickstart-6.parsed.json XXX.summary.json \uff1a\u6d4b\u8bd5\u62a5\u544a\u751f\u6210\u524d\u7684\u6570\u636e\u7ed3\u6784\u5185\u5bb9\uff0c\u4f8b\u5982 demo-quickstart-6.summary.json","title":"\u4fdd\u5b58\u8be6\u7ec6\u8fc7\u7a0b\u6570\u636e"},{"location":"run-tests/load-test/","text":"HttpRunner \u901a\u8fc7\u590d\u7528 Locust \uff0c\u53ef\u4ee5\u5728\u65e0\u9700\u5bf9 YAML/JSON \u8fdb\u884c\u4efb\u4f55\u4fee\u6539\u7684\u60c5\u51b5\u4e0b\uff0c\u76f4\u63a5\u8fd0\u884c\u6027\u80fd\u6d4b\u8bd5\u3002 \u539f\u7406\u56fe \u00b6 \u5b89\u88c5\u4f9d\u8d56\u5305 \u00b6 \u5b89\u88c5\u5b8c\u6210 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e locusts \u547d\u4ee4\uff0c\u4f46\u4e0d\u4f1a\u540c\u65f6\u5b89\u88c5 Locust\u3002 \u5728\u7cfb\u7edf\u4e2d\u672a\u5b89\u88c5 Locust \u7684\u60c5\u51b5\u4e0b\uff0c\u8fd0\u884c locusts \u547d\u4ee4\u65f6\u4f1a\u51fa\u73b0\u5982\u4e0b\u63d0\u793a\u3002 $ locusts -V WARNING Locust is not installed, install first and try again. install command: pip install locustio Locust \u7684\u5b89\u88c5\u65b9\u5f0f\u5982\u4e0b\uff1a $ pip install locustio \u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u6267\u884c locusts -V \u53ef\u67e5\u770b\u5230 Locust \u7684\u7248\u672c\u53f7\u3002 $ locusts -V [2017-08-26 23:45:42,246] bogon/INFO/stdout: Locust 0.8a2 [2017-08-26 23:45:42,246] bogon/INFO/stdout: \u6267\u884c locusts -h \uff0c\u53ef\u67e5\u770b\u5230\u4f7f\u7528\u5e2e\u52a9\u6587\u6863\u3002 $ locusts -h Usage: locust [options] [LocustClass [LocustClass2 ... ]] Options: -h, --help show this help message and exit -H HOST, --host=HOST Host to load test in the following format: http://10.21.32.33 --web-host=WEB_HOST Host to bind the web interface to. Defaults to '' (all interfaces) -P PORT, --port=PORT, --web-port=PORT Port on which to run web host -f LOCUSTFILE, --locustfile=LOCUSTFILE Python module file to import, e.g. '../other.py'. Default: locustfile --csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE Store current request stats to files in CSV format. --master Set locust to run in distributed mode with this process as master --slave Set locust to run in distributed mode with this process as slave --master-host=MASTER_HOST Host or IP address of locust master for distributed load testing. Only used when running with --slave. Defaults to 127.0.0.1. --master-port=MASTER_PORT The port to connect to that is used by the locust master for distributed load testing. Only used when running with --slave. Defaults to 5557. Note that slaves will also connect to the master node on this port + 1. --master-bind-host=MASTER_BIND_HOST Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces). --master-bind-port=MASTER_BIND_PORT Port that locust master should bind to. Only used when running with --master. Defaults to 5557. Note that Locust will also use this port + 1, so by default the master node will bind to 5557 and 5558. --expect-slaves=EXPECT_SLAVES How many slaves master should expect to connect before starting the test (only when --no-web used). --no-web Disable the web interface, and instead start running the test immediately. Requires -c and -r to be specified. -c NUM_CLIENTS, --clients=NUM_CLIENTS Number of concurrent clients. Only used together with --no-web -r HATCH_RATE, --hatch-rate=HATCH_RATE The rate per second in which clients are spawned. Only used together with --no-web -n NUM_REQUESTS, --num-request=NUM_REQUESTS Number of requests to perform. Only used together with --no-web -L LOGLEVEL, --loglevel=LOGLEVEL Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO. --logfile=LOGFILE Path to log file. If not set, log will go to stdout/stderr --print-stats Print stats in the console --only-summary Only print the summary stats --no-reset-stats Do not reset statistics once hatching has been completed -l, --list Show list of possible locust classes and exit --show-task-ratio print table of the locust classes' task execution ratio --show-task-ratio-json print json data of the locust classes' task execution ratio -V, --version show program's version number and exit \u53ef\u4ee5\u770b\u51fa\uff0c loucsts \u547d\u4ee4\u4e0e locust \u547d\u4ee4\u7684\u7528\u6cd5\u57fa\u672c\u76f8\u540c\u3002 \u76f8\u6bd4\u4e8e locust \u547d\u4ee4\uff0c loucsts \u547d\u4ee4\u4e3b\u8981\u5b58\u5728\u5982\u4e0b\u4e24\u9879\u5dee\u5f02\u3002 \u8fd0\u884c\u6027\u80fd\u6d4b\u8bd5 \u00b6 \u5728 -f \u53c2\u6570\u540e\u9762\uff0c loucsts \u547d\u4ee4\u4e0d\u4ec5\u53ef\u4ee5\u6307\u5b9a Locust \u652f\u6301\u7684 Python \u6587\u4ef6\uff0c\u540c\u65f6\u53ef\u4ee5\u76f4\u63a5\u6307\u5b9a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002\u5728\u5177\u4f53\u5b9e\u73b0\u4e0a\uff0c\u5f53 -f \u6307\u5b9a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u65f6\uff0c\u4f1a\u5148\u5c06\u5176\u8f6c\u6362\u4e3a Python \u683c\u5f0f\u7684 locustfile\uff0c\u7136\u540e\u518d\u5c06 locustfile.py \u4f20\u7ed9 locust \u547d\u4ee4\u3002 $ locusts -f examples/first-testcase.yml [ 2017 -08-18 17 :20:43,915 ] Leos-MacBook-Air.local/INFO/locust.main: Starting web monitor at *:8089 [ 2017 -08-18 17 :20:43,918 ] Leos-MacBook-Air.local/INFO/locust.main: Starting Locust 0 .8a2 \u6267\u884c\u4e0a\u8ff0\u547d\u4ee4\u540e\uff0c\u5373\u5b8c\u6210\u4e86 Locust \u670d\u52a1\u7684\u542f\u52a8\uff0c\u540e\u7eed\u5c31\u53ef\u4ee5\u5728 Locust \u7684 Web \u7ba1\u7406\u754c\u9762\u4e2d\u8fdb\u884c\u64cd\u4f5c\u4e86\uff0c\u4f7f\u7528\u65b9\u5f0f\u4e0e Locust \u5b8c\u5168\u76f8\u540c\u3002 \u591a\u8fdb\u7a0b\u8fd0\u884c\u6a21\u5f0f \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5728 Locust \u4e2d\u5982\u9700\u4f7f\u7528 master-slave \u6a21\u5f0f\u542f\u52a8\u591a\u4e2a\u8fdb\u7a0b\uff08\u4f7f\u7528\u591a\u6838\u5904\u7406\u5668\u7684\u80fd\u529b\uff09\uff0c\u53ea\u80fd\u5148\u542f\u52a8 master\uff0c\u7136\u540e\u518d\u9010\u4e00\u542f\u52a8\u82e5\u5e72\u4e2a slave\u3002 $ locust -f locustfile.py --master $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & \u5728 HttpRunner \u4e2d\uff0c\u65b0\u589e\u5b9e\u73b0 --processes \u53c2\u6570\uff0c\u53ef\u4ee5\u4e00\u6b21\u6027\u542f\u52a8 1 \u4e2a master \u548c\u591a\u4e2a salve\u3002\u82e5\u5728 --processes \u53c2\u6570\u540e\u6ca1\u6709\u6307\u5b9a\u5177\u4f53\u7684\u6570\u503c\uff0c\u5219\u542f\u52a8\u7684 slave \u4e2a\u6570\u4e0e\u673a\u5668\u7684 CPU \u6838\u6570\u76f8\u540c\u3002 $ locusts -f examples/first-testcase.yml --processes 4 [ 2017 -08-26 23 :51:47,071 ] bogon/INFO/locust.main: Starting web monitor at *:8089 [ 2017 -08-26 23 :51:47,075 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,078 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,080 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,083 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,084 ] bogon/INFO/locust.runners: Client 'bogon_656e0af8e968a8533d379dd252422ad3' reported as ready. Currently 1 clients ready to swarm. [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_09f73850252ee4ec739ed77d3c4c6dba' reported as ready. Currently 2 clients ready to swarm. [ 2017 -08-26 23 :51:47,084 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_869f7ed671b1a9952b56610f01e2006f' reported as ready. Currently 3 clients ready to swarm. [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_80a804cda36b80fac17b57fd2d5e7cdb' reported as ready. Currently 4 clients ready to swarm. Enjoy!","title":"\u6027\u80fd\u6d4b\u8bd5"},{"location":"run-tests/load-test/#_1","text":"","title":"\u539f\u7406\u56fe"},{"location":"run-tests/load-test/#_2","text":"\u5b89\u88c5\u5b8c\u6210 HttpRunner \u540e\uff0c\u7cfb\u7edf\u4e2d\u4f1a\u65b0\u589e locusts \u547d\u4ee4\uff0c\u4f46\u4e0d\u4f1a\u540c\u65f6\u5b89\u88c5 Locust\u3002 \u5728\u7cfb\u7edf\u4e2d\u672a\u5b89\u88c5 Locust \u7684\u60c5\u51b5\u4e0b\uff0c\u8fd0\u884c locusts \u547d\u4ee4\u65f6\u4f1a\u51fa\u73b0\u5982\u4e0b\u63d0\u793a\u3002 $ locusts -V WARNING Locust is not installed, install first and try again. install command: pip install locustio Locust \u7684\u5b89\u88c5\u65b9\u5f0f\u5982\u4e0b\uff1a $ pip install locustio \u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u6267\u884c locusts -V \u53ef\u67e5\u770b\u5230 Locust \u7684\u7248\u672c\u53f7\u3002 $ locusts -V [2017-08-26 23:45:42,246] bogon/INFO/stdout: Locust 0.8a2 [2017-08-26 23:45:42,246] bogon/INFO/stdout: \u6267\u884c locusts -h \uff0c\u53ef\u67e5\u770b\u5230\u4f7f\u7528\u5e2e\u52a9\u6587\u6863\u3002 $ locusts -h Usage: locust [options] [LocustClass [LocustClass2 ... ]] Options: -h, --help show this help message and exit -H HOST, --host=HOST Host to load test in the following format: http://10.21.32.33 --web-host=WEB_HOST Host to bind the web interface to. Defaults to '' (all interfaces) -P PORT, --port=PORT, --web-port=PORT Port on which to run web host -f LOCUSTFILE, --locustfile=LOCUSTFILE Python module file to import, e.g. '../other.py'. Default: locustfile --csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE Store current request stats to files in CSV format. --master Set locust to run in distributed mode with this process as master --slave Set locust to run in distributed mode with this process as slave --master-host=MASTER_HOST Host or IP address of locust master for distributed load testing. Only used when running with --slave. Defaults to 127.0.0.1. --master-port=MASTER_PORT The port to connect to that is used by the locust master for distributed load testing. Only used when running with --slave. Defaults to 5557. Note that slaves will also connect to the master node on this port + 1. --master-bind-host=MASTER_BIND_HOST Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces). --master-bind-port=MASTER_BIND_PORT Port that locust master should bind to. Only used when running with --master. Defaults to 5557. Note that Locust will also use this port + 1, so by default the master node will bind to 5557 and 5558. --expect-slaves=EXPECT_SLAVES How many slaves master should expect to connect before starting the test (only when --no-web used). --no-web Disable the web interface, and instead start running the test immediately. Requires -c and -r to be specified. -c NUM_CLIENTS, --clients=NUM_CLIENTS Number of concurrent clients. Only used together with --no-web -r HATCH_RATE, --hatch-rate=HATCH_RATE The rate per second in which clients are spawned. Only used together with --no-web -n NUM_REQUESTS, --num-request=NUM_REQUESTS Number of requests to perform. Only used together with --no-web -L LOGLEVEL, --loglevel=LOGLEVEL Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO. --logfile=LOGFILE Path to log file. If not set, log will go to stdout/stderr --print-stats Print stats in the console --only-summary Only print the summary stats --no-reset-stats Do not reset statistics once hatching has been completed -l, --list Show list of possible locust classes and exit --show-task-ratio print table of the locust classes' task execution ratio --show-task-ratio-json print json data of the locust classes' task execution ratio -V, --version show program's version number and exit \u53ef\u4ee5\u770b\u51fa\uff0c loucsts \u547d\u4ee4\u4e0e locust \u547d\u4ee4\u7684\u7528\u6cd5\u57fa\u672c\u76f8\u540c\u3002 \u76f8\u6bd4\u4e8e locust \u547d\u4ee4\uff0c loucsts \u547d\u4ee4\u4e3b\u8981\u5b58\u5728\u5982\u4e0b\u4e24\u9879\u5dee\u5f02\u3002","title":"\u5b89\u88c5\u4f9d\u8d56\u5305"},{"location":"run-tests/load-test/#_3","text":"\u5728 -f \u53c2\u6570\u540e\u9762\uff0c loucsts \u547d\u4ee4\u4e0d\u4ec5\u53ef\u4ee5\u6307\u5b9a Locust \u652f\u6301\u7684 Python \u6587\u4ef6\uff0c\u540c\u65f6\u53ef\u4ee5\u76f4\u63a5\u6307\u5b9a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u3002\u5728\u5177\u4f53\u5b9e\u73b0\u4e0a\uff0c\u5f53 -f \u6307\u5b9a YAML/JSON \u683c\u5f0f\u7684\u6d4b\u8bd5\u7528\u4f8b\u6587\u4ef6\u65f6\uff0c\u4f1a\u5148\u5c06\u5176\u8f6c\u6362\u4e3a Python \u683c\u5f0f\u7684 locustfile\uff0c\u7136\u540e\u518d\u5c06 locustfile.py \u4f20\u7ed9 locust \u547d\u4ee4\u3002 $ locusts -f examples/first-testcase.yml [ 2017 -08-18 17 :20:43,915 ] Leos-MacBook-Air.local/INFO/locust.main: Starting web monitor at *:8089 [ 2017 -08-18 17 :20:43,918 ] Leos-MacBook-Air.local/INFO/locust.main: Starting Locust 0 .8a2 \u6267\u884c\u4e0a\u8ff0\u547d\u4ee4\u540e\uff0c\u5373\u5b8c\u6210\u4e86 Locust \u670d\u52a1\u7684\u542f\u52a8\uff0c\u540e\u7eed\u5c31\u53ef\u4ee5\u5728 Locust \u7684 Web \u7ba1\u7406\u754c\u9762\u4e2d\u8fdb\u884c\u64cd\u4f5c\u4e86\uff0c\u4f7f\u7528\u65b9\u5f0f\u4e0e Locust \u5b8c\u5168\u76f8\u540c\u3002","title":"\u8fd0\u884c\u6027\u80fd\u6d4b\u8bd5"},{"location":"run-tests/load-test/#_4","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u5728 Locust \u4e2d\u5982\u9700\u4f7f\u7528 master-slave \u6a21\u5f0f\u542f\u52a8\u591a\u4e2a\u8fdb\u7a0b\uff08\u4f7f\u7528\u591a\u6838\u5904\u7406\u5668\u7684\u80fd\u529b\uff09\uff0c\u53ea\u80fd\u5148\u542f\u52a8 master\uff0c\u7136\u540e\u518d\u9010\u4e00\u542f\u52a8\u82e5\u5e72\u4e2a slave\u3002 $ locust -f locustfile.py --master $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & $ locust -f locustfile.py --slave & \u5728 HttpRunner \u4e2d\uff0c\u65b0\u589e\u5b9e\u73b0 --processes \u53c2\u6570\uff0c\u53ef\u4ee5\u4e00\u6b21\u6027\u542f\u52a8 1 \u4e2a master \u548c\u591a\u4e2a salve\u3002\u82e5\u5728 --processes \u53c2\u6570\u540e\u6ca1\u6709\u6307\u5b9a\u5177\u4f53\u7684\u6570\u503c\uff0c\u5219\u542f\u52a8\u7684 slave \u4e2a\u6570\u4e0e\u673a\u5668\u7684 CPU \u6838\u6570\u76f8\u540c\u3002 $ locusts -f examples/first-testcase.yml --processes 4 [ 2017 -08-26 23 :51:47,071 ] bogon/INFO/locust.main: Starting web monitor at *:8089 [ 2017 -08-26 23 :51:47,075 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,078 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,080 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,083 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,084 ] bogon/INFO/locust.runners: Client 'bogon_656e0af8e968a8533d379dd252422ad3' reported as ready. Currently 1 clients ready to swarm. [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_09f73850252ee4ec739ed77d3c4c6dba' reported as ready. Currently 2 clients ready to swarm. [ 2017 -08-26 23 :51:47,084 ] bogon/INFO/locust.main: Starting Locust 0 .8a2 [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_869f7ed671b1a9952b56610f01e2006f' reported as ready. Currently 3 clients ready to swarm. [ 2017 -08-26 23 :51:47,085 ] bogon/INFO/locust.runners: Client 'bogon_80a804cda36b80fac17b57fd2d5e7cdb' reported as ready. Currently 4 clients ready to swarm. Enjoy!","title":"\u591a\u8fdb\u7a0b\u8fd0\u884c\u6a21\u5f0f"},{"location":"run-tests/report/","text":"\u4f7f\u7528 HttpRunner \u6267\u884c\u5b8c\u81ea\u52a8\u5316\u6d4b\u8bd5\u540e\uff0c\u4f1a\u5728\u5f53\u524d\u8def\u5f84\u7684 reports \u76ee\u5f55\u4e0b\u751f\u6210\u4e00\u4efd HTML \u683c\u5f0f\u7684\u6d4b\u8bd5\u62a5\u544a\u3002 \u9ed8\u8ba4\u60c5\u51b5 \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u4f1a\u4f4d\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684 reports \u6587\u4ef6\u5939\u4e2d\uff0c\u6587\u4ef6\u540d\u79f0\u4e3a\u6d4b\u8bd5\u5f00\u59cb\u7684\u65f6\u95f4\u6233\u3002 $ hrun docs/data/demo-quickstart-6.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200 , response_time ( ms ) : 10 .05 ms, response_length: 46 bytes . /api/users/1548561170497 INFO POST http://127.0.0.1:5000/api/users/1548561170497 INFO status_code: 201 , response_time ( ms ) : 2 .88 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0 .034s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548561170.html \u70b9\u51fb\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u3002 \u9ed8\u8ba4\u62a5\u544a\u6837\u5f0f \u00b6 \u5728 HttpRunner \u4e2d\u81ea\u5e26\u4e86\u4e00\u4e2a Jinja2 \u683c\u5f0f\u7684\u62a5\u544a\u6a21\u7248\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u62a5\u544a\u6837\u5f0f\u5747\u57fa\u4e8e\u8be5\u6a21\u7248\uff08 httprunner/templates/default_report_template.html \uff09\u3002 \u6d4b\u8bd5\u62a5\u544a\u5f62\u5f0f\u5982\u4e0b\uff1a \u5728 Summary \u4e2d\uff0c\u4f1a\u7f57\u5217\u672c\u6b21\u6d4b\u8bd5\u7684\u6574\u4f53\u4fe1\u606f\uff0c\u5305\u62ec\u6d4b\u8bd5\u5f00\u59cb\u65f6\u95f4\u3001\u603b\u8fd0\u884c\u65f6\u957f\u3001\u8fd0\u884c\u7684Python\u7248\u672c\u548c\u7cfb\u7edf\u73af\u5883\u3001\u8fd0\u884c\u7ed3\u679c\u7edf\u8ba1\u6570\u636e\u3002 \u5728 Details \u4e2d\uff0c\u4f1a\u8be6\u7ec6\u5c55\u793a\u6bcf\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u7ed3\u679c\u3002 \u70b9\u51fb\u6d4b\u8bd5\u7528\u4f8b\u5bf9\u5e94\u7684 log \u6309\u94ae\uff0c\u4f1a\u5728\u5f39\u51fa\u6846\u4e2d\u5c55\u793a\u8be5\u7528\u4f8b\u6267\u884c\u7684\u8be6\u7ec6\u6570\u636e\uff0c\u5305\u62ec\u8bf7\u6c42\u7684 headers \u548c body\u3001\u54cd\u5e94\u7684 headers \u548c body\u3001\u6821\u9a8c\u7ed3\u679c\u3001\u54cd\u5e94\u3001\u54cd\u5e94\u8017\u65f6\uff08elapsed\uff09\u7b49\u4fe1\u606f\u3002 \u82e5\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u4e0d\u6210\u529f\uff08failed/error/skipped\uff09\uff0c\u5219\u5728\u8be5\u6d4b\u8bd5\u7528\u4f8b\u7684 detail \u4e2d\u4f1a\u51fa\u73b0 traceback \u6309\u94ae\uff0c\u70b9\u51fb\u8be5\u6309\u94ae\u540e\uff0c\u4f1a\u5728\u5f39\u51fa\u6846\u4e2d\u5c55\u793a\u5931\u8d25\u7684\u5806\u6808\u65e5\u5fd7\uff0c\u6216\u8005 skipped \u7684\u539f\u56e0\u3002 \u70b9\u51fb\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u3002 \u81ea\u5b9a\u4e49 \u00b6 \u9664\u4e86\u9ed8\u8ba4\u7684\u62a5\u544a\u6837\u5f0f\uff0cHttpRunner \u8fd8\u652f\u6301\u4f7f\u7528\u81ea\u5b9a\u4e49\u7684\u62a5\u544a\u6a21\u677f\u3002 \u7f16\u5199\u81ea\u5b9a\u4e49\u6a21\u677f\uff08Jinja2\u683c\u5f0f\uff09 \u00b6 \u81ea\u5b9a\u4e49\u6a21\u677f\u9700\u8981\u91c7\u7528 Jinja2 \u7684\u683c\u5f0f\uff0c\u5176\u4e2d\u53ef\u4ee5\u4f7f\u7528\u7684\u6570\u636e\u53ef\u53c2\u8003 \u6570\u636e\u7ed3\u6784\u793a\u4f8b \u3002 \u4f8b\u5982\uff0c\u6211\u4eec\u9700\u8981\u5728\u81ea\u5b9a\u4e49\u6a21\u677f\u4e2d\u5c55\u793a\u6d4b\u8bd5\u7ed3\u679c\u7684\u7edf\u8ba1\u6570\u636e\uff0c\u5c31\u53ef\u4ee5\u91c7\u7528\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u63cf\u8ff0\uff1a < tr > < th > TOTAL < th > SUCCESS < th > FAILED < th > ERROR < th > SKIPPED < tr > < td > {{stat.testsRun}} < td > {{stat.successes}} < td > {{stat.failures}} < td > {{stat.errors}} < td > {{stat.skipped}} \u5728\u81ea\u5b9a\u4e49\u62a5\u544a\u6a21\u677f\u65f6\uff0c\u53ef\u4ee5\u53c2\u8003 HttpRunner \u7684 \u9ed8\u8ba4\u62a5\u544a\u6a21\u677f \uff0c\u8981\u5b9e\u73b0\u66f4\u590d\u6742\u7684\u6a21\u7248\u529f\u80fd\uff0c\u53ef\u53c2\u8003 Jinja2 \u7684\u4f7f\u7528\u6587\u6863\u3002 \u4f7f\u7528\u81ea\u5b9a\u4e49\u6a21\u677f \u00b6 \u4f7f\u7528\u81ea\u5b9a\u4e49\u6a21\u7248\u65f6\uff0c\u9700\u8981\u901a\u8fc7 --report-template \u6307\u5b9a\u62a5\u544a\u6a21\u677f\u7684\u8def\u5f84\uff0c\u7136\u540e\u6d4b\u8bd5\u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u5c31\u4f1a\u91c7\u7528\u81ea\u5b9a\u4e49\u7684\u6a21\u677f\u751f\u6210\u6d4b\u8bd5\u62a5\u544a\u3002 $ hrun docs/data/demo-quickstart-2.yml --report-template /path/to/custom_report_template ... \u540c\u4e0a\uff0c\u7701\u7565 INFO render with html report template: /path/to/custom_report_template INFO Start to render Html report ... INFO Generated Html report: reports/1532078874.html \u6307\u5b9a\u62a5\u544a\u751f\u6210\u8def\u5f84 \u00b6 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u4f1a\u4f4d\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684 reports \u6587\u4ef6\u5939\u4e2d\u3002\u5982\u9700\u6307\u5b9a\u751f\u6210\u62a5\u544a\u7684\u8def\u5f84\uff0c\u53ef\u4ee5\u4f7f\u7528 --report-dir \u53c2\u6570\u3002 $ hrun docs/data/demo-quickstart-2.yml --dirreport-name /other/path/ ... \u540c\u4e0a\uff0c\u7701\u7565 INFO Start to render Html report ... INFO Generated Html report: /other/path/1532078874.html","title":"\u6d4b\u8bd5\u62a5\u544a"},{"location":"run-tests/report/#_1","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u4f1a\u4f4d\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684 reports \u6587\u4ef6\u5939\u4e2d\uff0c\u6587\u4ef6\u540d\u79f0\u4e3a\u6d4b\u8bd5\u5f00\u59cb\u7684\u65f6\u95f4\u6233\u3002 $ hrun docs/data/demo-quickstart-6.yml INFO Start to run testcase: testcase description /api/get-token INFO POST http://127.0.0.1:5000/api/get-token INFO status_code: 200 , response_time ( ms ) : 10 .05 ms, response_length: 46 bytes . /api/users/1548561170497 INFO POST http://127.0.0.1:5000/api/users/1548561170497 INFO status_code: 201 , response_time ( ms ) : 2 .88 ms, response_length: 54 bytes . ---------------------------------------------------------------------- Ran 2 tests in 0 .034s OK INFO Start to render Html report ... INFO Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548561170.html \u70b9\u51fb\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u3002","title":"\u9ed8\u8ba4\u60c5\u51b5"},{"location":"run-tests/report/#_2","text":"\u5728 HttpRunner \u4e2d\u81ea\u5e26\u4e86\u4e00\u4e2a Jinja2 \u683c\u5f0f\u7684\u62a5\u544a\u6a21\u7248\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u62a5\u544a\u6837\u5f0f\u5747\u57fa\u4e8e\u8be5\u6a21\u7248\uff08 httprunner/templates/default_report_template.html \uff09\u3002 \u6d4b\u8bd5\u62a5\u544a\u5f62\u5f0f\u5982\u4e0b\uff1a \u5728 Summary \u4e2d\uff0c\u4f1a\u7f57\u5217\u672c\u6b21\u6d4b\u8bd5\u7684\u6574\u4f53\u4fe1\u606f\uff0c\u5305\u62ec\u6d4b\u8bd5\u5f00\u59cb\u65f6\u95f4\u3001\u603b\u8fd0\u884c\u65f6\u957f\u3001\u8fd0\u884c\u7684Python\u7248\u672c\u548c\u7cfb\u7edf\u73af\u5883\u3001\u8fd0\u884c\u7ed3\u679c\u7edf\u8ba1\u6570\u636e\u3002 \u5728 Details \u4e2d\uff0c\u4f1a\u8be6\u7ec6\u5c55\u793a\u6bcf\u4e00\u6761\u6d4b\u8bd5\u7528\u4f8b\u7684\u8fd0\u884c\u7ed3\u679c\u3002 \u70b9\u51fb\u6d4b\u8bd5\u7528\u4f8b\u5bf9\u5e94\u7684 log \u6309\u94ae\uff0c\u4f1a\u5728\u5f39\u51fa\u6846\u4e2d\u5c55\u793a\u8be5\u7528\u4f8b\u6267\u884c\u7684\u8be6\u7ec6\u6570\u636e\uff0c\u5305\u62ec\u8bf7\u6c42\u7684 headers \u548c body\u3001\u54cd\u5e94\u7684 headers \u548c body\u3001\u6821\u9a8c\u7ed3\u679c\u3001\u54cd\u5e94\u3001\u54cd\u5e94\u8017\u65f6\uff08elapsed\uff09\u7b49\u4fe1\u606f\u3002 \u82e5\u6d4b\u8bd5\u7528\u4f8b\u8fd0\u884c\u4e0d\u6210\u529f\uff08failed/error/skipped\uff09\uff0c\u5219\u5728\u8be5\u6d4b\u8bd5\u7528\u4f8b\u7684 detail \u4e2d\u4f1a\u51fa\u73b0 traceback \u6309\u94ae\uff0c\u70b9\u51fb\u8be5\u6309\u94ae\u540e\uff0c\u4f1a\u5728\u5f39\u51fa\u6846\u4e2d\u5c55\u793a\u5931\u8d25\u7684\u5806\u6808\u65e5\u5fd7\uff0c\u6216\u8005 skipped \u7684\u539f\u56e0\u3002 \u70b9\u51fb\u67e5\u770b \u6d4b\u8bd5\u62a5\u544a \u3002","title":"\u9ed8\u8ba4\u62a5\u544a\u6837\u5f0f"},{"location":"run-tests/report/#_3","text":"\u9664\u4e86\u9ed8\u8ba4\u7684\u62a5\u544a\u6837\u5f0f\uff0cHttpRunner \u8fd8\u652f\u6301\u4f7f\u7528\u81ea\u5b9a\u4e49\u7684\u62a5\u544a\u6a21\u677f\u3002","title":"\u81ea\u5b9a\u4e49"},{"location":"run-tests/report/#jinja2","text":"\u81ea\u5b9a\u4e49\u6a21\u677f\u9700\u8981\u91c7\u7528 Jinja2 \u7684\u683c\u5f0f\uff0c\u5176\u4e2d\u53ef\u4ee5\u4f7f\u7528\u7684\u6570\u636e\u53ef\u53c2\u8003 \u6570\u636e\u7ed3\u6784\u793a\u4f8b \u3002 \u4f8b\u5982\uff0c\u6211\u4eec\u9700\u8981\u5728\u81ea\u5b9a\u4e49\u6a21\u677f\u4e2d\u5c55\u793a\u6d4b\u8bd5\u7ed3\u679c\u7684\u7edf\u8ba1\u6570\u636e\uff0c\u5c31\u53ef\u4ee5\u91c7\u7528\u5982\u4e0b\u65b9\u5f0f\u8fdb\u884c\u63cf\u8ff0\uff1a < tr > < th > TOTAL < th > SUCCESS < th > FAILED < th > ERROR < th > SKIPPED < tr > < td > {{stat.testsRun}} < td > {{stat.successes}} < td > {{stat.failures}} < td > {{stat.errors}} < td > {{stat.skipped}} \u5728\u81ea\u5b9a\u4e49\u62a5\u544a\u6a21\u677f\u65f6\uff0c\u53ef\u4ee5\u53c2\u8003 HttpRunner \u7684 \u9ed8\u8ba4\u62a5\u544a\u6a21\u677f \uff0c\u8981\u5b9e\u73b0\u66f4\u590d\u6742\u7684\u6a21\u7248\u529f\u80fd\uff0c\u53ef\u53c2\u8003 Jinja2 \u7684\u4f7f\u7528\u6587\u6863\u3002","title":"\u7f16\u5199\u81ea\u5b9a\u4e49\u6a21\u677f\uff08Jinja2\u683c\u5f0f\uff09"},{"location":"run-tests/report/#_4","text":"\u4f7f\u7528\u81ea\u5b9a\u4e49\u6a21\u7248\u65f6\uff0c\u9700\u8981\u901a\u8fc7 --report-template \u6307\u5b9a\u62a5\u544a\u6a21\u677f\u7684\u8def\u5f84\uff0c\u7136\u540e\u6d4b\u8bd5\u8fd0\u884c\u5b8c\u6210\u540e\uff0c\u5c31\u4f1a\u91c7\u7528\u81ea\u5b9a\u4e49\u7684\u6a21\u677f\u751f\u6210\u6d4b\u8bd5\u62a5\u544a\u3002 $ hrun docs/data/demo-quickstart-2.yml --report-template /path/to/custom_report_template ... \u540c\u4e0a\uff0c\u7701\u7565 INFO render with html report template: /path/to/custom_report_template INFO Start to render Html report ... INFO Generated Html report: reports/1532078874.html","title":"\u4f7f\u7528\u81ea\u5b9a\u4e49\u6a21\u677f"},{"location":"run-tests/report/#_5","text":"\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u751f\u6210\u7684\u6d4b\u8bd5\u62a5\u544a\u6587\u4ef6\u4f1a\u4f4d\u4e8e\u9879\u76ee\u6839\u76ee\u5f55\u7684 reports \u6587\u4ef6\u5939\u4e2d\u3002\u5982\u9700\u6307\u5b9a\u751f\u6210\u62a5\u544a\u7684\u8def\u5f84\uff0c\u53ef\u4ee5\u4f7f\u7528 --report-dir \u53c2\u6570\u3002 $ hrun docs/data/demo-quickstart-2.yml --dirreport-name /other/path/ ... \u540c\u4e0a\uff0c\u7701\u7565 INFO Start to render Html report ... INFO Generated Html report: /other/path/1532078874.html","title":"\u6307\u5b9a\u62a5\u544a\u751f\u6210\u8def\u5f84"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..a2e66b38 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,108 @@ + + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + + None + 2019-11-01 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 00000000..3cdf18ad Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/sponsors/index.html b/sponsors/index.html new file mode 100644 index 00000000..dc8aa38e --- /dev/null +++ b/sponsors/index.html @@ -0,0 +1,773 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 赞助商 - HttpRunner V2.x 中文使用文档 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳转至 + + + +
+ +
+ +
+ + + + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + + +

赞助商

+

感谢各位对 HttpRunner 的赞助支持!

+

金牌赞助商(Gold Sponsor)

+

霍格沃兹测试学院

+
+

霍格沃兹测试学院 是由测吧(北京)科技有限公司与知名软件测试社区 TesterHome 合作的高端教育品牌。由 BAT 一线测试大咖执教,提供实战驱动的接口自动化测试、移动自动化测试、性能测试、持续集成与 DevOps 等技术培训,以及测试开发优秀人才内推服务。点击学习!

+
+

霍格沃兹测试学院是 HttpRunner 的首家金牌赞助商。

+

成为赞助商

+

如果你所在的公司或个人也想对 HttpRunner 进行赞助,可参考如下方案,具体可联系项目作者

+ + + + + + + + + + + + + + + + + + + + + + + +
等级金牌赞助商
(Gold Sponsor)
银牌赞助商
(Silver Sponsor)
个人赞赏
金额¥10000/年¥5000/年任意
权益公司 logo(大)和链接展示在 README.md
150 字的宣传文案
公司 logo(中)和链接展示在 README.md
50 字的宣传文案
个人 ID 和链接展示在 sponsors.md
+ + + + + + + + + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file