├── .editorconfig ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── CHANGE.md ├── LICENSE ├── README.md ├── README_zh-cn.md ├── bin └── uirecorder ├── buildcrx.bat ├── buildcrx.sh ├── chrome-extension ├── img │ ├── alert.png │ ├── back.png │ ├── cancel.png │ ├── end.png │ ├── expect.png │ ├── fail.png │ ├── hover.png │ ├── icon-128.png │ ├── icon-64.png │ ├── icon-disable.png │ ├── icon-record.png │ ├── icon.png │ ├── jscode.png │ ├── jump.png │ ├── mloading.gif │ ├── ok.png │ ├── sleep.png │ ├── success.png │ ├── text.png │ ├── vars.png │ └── warn.png ├── js │ ├── background.js │ ├── foreground.js │ ├── jquery-3.4.1.min.js │ ├── mobile.js │ └── start.js ├── manifest.json ├── mobile.html └── start.html ├── doc └── zh-cn │ ├── pc-advanced.md │ ├── pc-faq.md │ ├── pc-standard.md │ └── readme.md ├── i18n ├── en.js ├── zh-cn.js └── zh-tw.js ├── index.js ├── lib ├── builder │ └── java.js ├── i18n.js ├── init.js ├── start.js └── update.js ├── logo.png ├── package-lock.json ├── package.json ├── project ├── .editorconfig ├── .gitignore1 ├── README.md ├── commons │ └── commons.md ├── hosts ├── install.sh ├── package.json ├── run.bat ├── run.sh ├── screenshots │ └── screenshots.md ├── uploadfiles │ └── uploadfiles.md └── vslaunch.json ├── screenshot ├── shot1.png ├── shot2.png ├── shot3.png └── shot4.png ├── template ├── javaTemplate.java ├── jwebdriver-mobile.js └── jwebdriver.js ├── tool └── uirecorder.crx ├── uirecorder.log └── uirecorder.pem /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | # Apply for all files 8 | [*] 9 | 10 | charset = utf-8 11 | 12 | indent_style = space 13 | indent_size = 4 14 | 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | test 4 | *.sw* 5 | *.un~ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | test 4 | chrome-extension 5 | buildcrx.bat 6 | uirecorder.pem 7 | screenshot 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug UIRecorder Local", 11 | "cwd": "${workspaceRoot}/test/test", 12 | "args": [ "${file}"], 13 | "console": "integratedTerminal" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CHANGE.md: -------------------------------------------------------------------------------- 1 | UI Recorder change log 2 | ==================== 3 | ## ver 3.5.3(2020-10-29) 4 | 1.Fix:Debugger Mode 5 | 6 | ## ver 3.4.3(2020-07-28) 7 | 1.Fix: chrome options default config 8 | 9 | ## ver 3.3.0(2020-02-22) 10 | 1.Feat: support mobile emulator 11 | 12 | ## ver 3.2.0(2020-01-20) 13 | 1.Feat: support customer reporter dirname 14 | 15 | ## ver 3.1.10(2020-01-19) 16 | 1.Feat: modify mobile template 17 | 18 | ## ver 3.1.9(2020-01-15) 19 | 1.Feat: support group tag template 20 | 21 | ## ver 3.1.8(2019-12-21) 22 | 1.Feat: update chromedriver version latest 23 | 24 | ## ver 3.1.7(2019-10-23) 25 | 1.Feat:update chromedriver version 77.0.0 26 | 27 | ## ver 3.1.6(2019-08-28) 28 | 1.Feat: support specific symbol in Chinese 29 | 30 | ## ver 3.1.5(2019-08-28) 31 | 1.Feat: by default, process always exit 0 when test fail 32 | 33 | ## ver 3.1.4(2019-08-28) 34 | 1.Feat: by default, process always exit 0 when test fail 35 | 36 | ## ver 3.1.3(2019-07-08) 37 | 1.Feat:support module test 38 | 39 | ## ver 3.1.2(2019-07-31) 40 | 1.Feat:chromedriver version 76.0.0 41 | 42 | ## ver 3.1.1(2019-07-30) 43 | 1.Fix: vscode lauch 44 | 45 | ## ver 3.1.0(2019-07-16) 46 | 1.Feature: screenshots image url use relative path 47 | 48 | ## ver 3.0.3(2019-07-13) 49 | 1.Feature: support jquery syntax 50 | 51 | ## ver 3.0.2(2019-07-12) 52 | 1.Feature: remove chrome-extension input size 53 | 54 | ## ver 3.0.1(2019-07-09) 55 | 1.Feature: modify macaca-reporter 56 | 57 | ## ver 3.0.0(2019-06-20) 58 | 1.Feature: mocha@3 -> mocha@5 59 | 2.Feature: mocha-parllel-tests@1.x -> mocha-parallel-tests@2.x 60 | 3.Feature: mochawesome-uirecorder -> macaca-reporter 61 | 62 | ## ver 2.6.0(2019-05-16) 63 | 1.Feature: remove default chromeOptions in uirecorder template 64 | 65 | ## ver 2.5.47(2019-02-22) 66 | 1.Feature: support window size maximize 67 | 68 | ## ver 2.5.46 (2019-01-07) 69 | 1.Feature: support custom template 70 | 71 | ## ver 2.5.45 (2018-11-14) 72 | 1.Fix: use windowSize(1024, 768) instead of maximize() default 73 | 2.Update: update dependencies version 74 | 75 | ## ver 2.5.43 (2018-10-19) 76 | 1.Fix: catch error infos when browser.maximize() throw error 77 | 78 | ## ver 2.5.42 (2018-5-19) 79 | 80 | 1. Fix: skip node_modules directory 81 | 82 | ## ver 2.5.41 (2018-5-4) 83 | 84 | 1. Fix: fix chromedriver issue 85 | 2. Fix: fix rand port issue for mobile mode 86 | 87 | ## ver 2.5.40 (2018-3-6) 88 | 89 | 1. Add: support define window open order when start record 90 | 91 | ## ver 2.5.39 (2018-3-1) 92 | 93 | 1. fix issue for 2.5.37 94 | 95 | ## ver 2.5.38 (2018-2-26) 96 | 97 | 1. fix issue for 2.5.37 98 | 2. fix issue: above & below not support number compare 99 | 100 | ## ver 2.5.37 (2018-2-9) 101 | 102 | 1. Add: support open checker browser and set maximize by default (uirecorder --default) 103 | 104 | ## ver 2.5.36 (2017-12-15) 105 | 106 | 1. Improve: change to random listen port 107 | 2. Add: support read wdproxy from env when recording 108 | 3. Fix: not escape the regular expression when add expect 109 | 110 | ## ver 2.5.35 (2017-11-1) 111 | 112 | 1. Add: support detect chromedriver bin file 113 | 2. Fix: kill uirecorder after chromedriver killed 114 | 115 | ## ver 2.5.34 (2017-10-25) 116 | 117 | 1. Add: bail after first test failure 118 | 2. Fix: support save acceptAlert after cmd beforeunload 119 | 3. Add: support not copy screenshot when no failed `--reporter-options copyShotOnlyFail=true` 120 | 121 | ## ver 2.5.33 (2017-9-20) 122 | 123 | 1. Add: add `value` to attr switch 124 | 125 | ## ver 2.5.32 (2017-9-14) 126 | 127 | 1. Fix: fix text path issue when contain space 128 | 129 | ## ver 2.5.31 (2017-9-7) 130 | 131 | 1. Fix: fix double event issue when change black value list 132 | 2. Add: add document 133 | 134 | ## ver 2.5.30 (2017-9-4) 135 | 136 | 1. Fix: fix update var issue after load module 137 | 138 | ## ver 2.5.29 (2017-9-1) 139 | 140 | 1. Add: add black list for text path(exclude special character: ×) 141 | 1. Add: save all cookies info to `xxx.cookie` when get screenshot 142 | 143 | ## ver 2.5.28 (2017-8-31) 144 | 145 | 1. Fix: fix issue when get unexpected alert msg #138 146 | 147 | ## ver 2.5.27 (2017-8-15) 148 | 149 | 1. Add: show version in recorder pannel 150 | 2. Add: support text path for pc mode #85 151 | 152 | ## ver 2.5.26 (2017-8-15) 153 | 154 | 1. Fix: fix a click issue for pc mode 155 | 156 | ## ver 2.5.25 (2017-8-11) 157 | 158 | 1. Fix: support remote url with any extension name #132 159 | 2. Add: support paste text for mobile mode 160 | 3. Fix: fix init proxy issue #124 161 | 162 | ## ver 2.5.24 (2017-8-9) 163 | 164 | 1. Fix: delete `\r` for windows, when paste multi line 165 | 166 | ## ver 2.5.23 (2017-8-7) 167 | 168 | 1. Add: support define device name for mobile mode 169 | 2. Add: support proxy for init cmd (read config from cnpm) #124 170 | 3. Add: support set proxy for pc uicase 171 | 172 | ## ver 2.5.22 (2017-7-31) 173 | 174 | 1. Fix: fix drag issue again #125 175 | 2. Add: show error message when parse config file failed 176 | 177 | ## ver 2.5.21 (2017-7-28) 178 | 179 | 1. Fix: fix drag issue #125 180 | 181 | ## ver 2.5.20 (2017-7-25) 182 | 183 | 1. Fix: fix expect jscode issue #119 184 | 185 | ## ver 2.5.19 (2017-7-25) 186 | 187 | 1. Fix: fix load spec failed when expect image diff #123 188 | 2. Add: support spec jump for mobile mode 189 | 3. Add: save test file when click save button only 190 | 191 | ## ver 2.5.18 (2017-7-24) 192 | 193 | 1. Fix: fix xpath issue for ios 194 | 195 | ## ver 2.5.16 (2017-7-24) 196 | 197 | 1. Add: support class value black list 198 | 2. Fix: fix some record failed issue for ios 199 | 200 | ## ver 2.5.15 (2017-7-6) 201 | 202 | 1. Add: support expect count for mobile mode 203 | 204 | ## ver 2.5.14 (2017-7-5) 205 | 206 | 1. Fix: change to use cnpm registry when init project, more faster, more stable 207 | 208 | ## ver 2.5.13 (2017-6-30) 209 | 210 | 1. Add: support image diff for mobile mode 211 | 212 | ## ver 2.5.12 (2017-6-28) 213 | 214 | 1. Fix: fix jump issue #116 215 | 216 | ## ver 2.5.11 (2017-6-22) 217 | 218 | 1. Fix: fix sleep&jscode issue when work with checker mode 219 | 2. Fix: fix eval issue 220 | 3. Add: add url info to html log file 221 | 222 | ## ver 2.5.10 (2017-6-13) 223 | 224 | 1. Add: support save log to `uirecorder.log` after test record 225 | 2. Add: support expect with image diff 226 | 227 | ## ver 2.5.9 (2017-6-6) 228 | 229 | 1. Add: support insert single template string without add variable 230 | 2. Add: support var template when eval jscode, `document.title="{{varname}}";` 231 | 3. Fix: support chrome v59 232 | 4. Add: support expect dom count 233 | 234 | ## ver 2.5.8 (2017-6-5) 235 | 236 | 1. Fix: fix continue record when pass filename from cli 237 | 2. Improve: improve hover mode, use single hover mode by default 238 | 239 | ## ver 2.5.7 (2017-6-2) 240 | 241 | 1. Add: support save source code when save screenshot 242 | 243 | ## ver 2.5.6 (2017-5-27) 244 | 245 | 1. Improve: support auto hover mode 246 | 2. Add: support expect the result after js eval in front browser 247 | 248 | ## ver 2.5.5 (2017-5-23) 249 | 250 | 1. Fix: Support new version of macaca ios driver 251 | 252 | ## ver 2.5.4 (2017-5-17) 253 | 254 | 1. Fix: Support new version of macaca android driver 255 | 256 | ## ver 2.5.3 (2017-5-11) 257 | 258 | 1. Support new version chromedriver 259 | 260 | ## ver 2.5.2 (2017-5-9) 261 | 262 | 1. Fix: fix notContain 263 | 264 | ## ver 2.5.1 (2017-5-9) 265 | 266 | 1. Add: Support debug local 267 | 268 | ## ver 2.5.0 (2017-5-8) 269 | 270 | 1. Add: Merge start and init command to default command, just use `uirecorder` command 271 | 2. Add: support es7 async 272 | 3. Add: support debug for vscode 273 | 4. Add: support notContain for expect (by stevobm) 274 | 5. Add: support install project dependencies and webdriver dependencies when init project 275 | 6. Add: skip filename input step when pass from cmd args 276 | 277 | ## ver 2.4.14 (2017-4-18) 278 | 279 | 1. Update: update chromedriver to v2.29.0 280 | 281 | ## ver 2.4.12 (2017-4-10) 282 | 283 | 1. Fix: hidden recorder tool pannel when loading module 284 | 285 | ## ver 2.4.11 (2017-4-1) 286 | 287 | 1. Fix: Support macaca new wda source api 288 | 289 | ## ver 2.4.10 (2017-3-29) 290 | 291 | 1. Fix: No reset browser size when continue recording 292 | 293 | ## ver 2.4.9 (2017-3-21) 294 | 295 | 1. Fix: data-testid not work 296 | 297 | ## ver 2.4.8 (2017-3-21) 298 | 299 | 1. Fix: disable get screenshot when closeWindow 300 | 2. Fix: insert new var failed in other iframe context 301 | 302 | ## ver 2.4.7 (2017-3-20) 303 | 304 | 1. Fix: insert var failed when add new var in iframe context 305 | 2. Add: support get data-testid before get id 306 | 3. Add: add new feature eval jscode in browser side 307 | 308 | ## ver 2.4.6 (2017-3-18) 309 | 310 | 1. Add: add localhost hosts tip for mac system 311 | 2. Update: update selenium-standalone to version v6.1.0 312 | 3. Fix: stop chromedriver and browser before recorder ended 313 | 4. Fix: add i18n text for var template dialog 314 | 5. Add: add title to recorder browser and checker browser 315 | 316 | ## ver 2.4.5 (2017-3-17) 317 | 318 | 1. Add: add attr data-test 319 | 2. Add: support auto show text dialog when mobile recording 320 | 3. Add: support js template string to insert var(pc), jump url(pc), send keys(mobile), expect(pc, mobile) 321 | 4. Del: delete support to faker.js 322 | 5. Fix: support to chrome v57 323 | 324 | ## ver 2.4.4 (2017-3-14) 325 | 326 | 1. Fix: not record sendKeys when paste in recorder dom area 327 | 328 | ## ver 2.4.3 (2017-3-7) 329 | 330 | 1. Fix: delay 1 second to init recorder browser 331 | 332 | ## ver 2.4.2 (2017-3-7) 333 | 334 | 1. Fix: fix update var failed in next page when recording 335 | 336 | ## ver 2.4.1 (2017-3-6) 337 | 338 | 1. Add: support save paste text when record pc test 339 | 340 | ## ver 2.4.0 (2017-3-6) 341 | 342 | 1. Fix: fix continue record issue when json file is missing 343 | 2. Remove: remove runtime 344 | 3. Update: support new version of macaca 345 | 4. Add: support new feature for mobile record: sleep, text, back, alert, expect, end 346 | 5. Add: support ios real device 347 | 6. Add: support download app file from url 348 | 7. Add: support continue record for mobile 349 | 350 | ## ver 2.3.32 (2017-2-17) 351 | 352 | 1. Fix: fix continue record issue 353 | 2. Fix: not save file when record zero step 354 | 3. Add: support optionClick 355 | 356 | ## ver 2.3.31 (2017-2-14) 357 | 358 | 1. Fix: add escape to module name when call spec 359 | 2. Add: support continue record 360 | 361 | ## ver 2.3.30 (2017-2-10) 362 | 363 | 1. Fix: fix raw path issue 364 | 2. Add: support disable id and name when recording 365 | 3. Add: support edit attr value black when recording 366 | 4. Add: support add sleep time 367 | 368 | ## ver 2.3.29 (2017-2-10) 369 | 370 | 1. Add: support save raw cmd json file 371 | 372 | ## ver 2.3.28 (2017-2-9) 373 | 374 | 1. Add: support expect after hover in mac os 375 | 2. Fix: fix some case skiped issue 376 | 3. Add: support show hosts in html reporter 377 | 4. Add: support use unicode file name for test case 378 | 5. Add: support start selenium-standalone server by npm cmd: `npm run server` 379 | 6. Fix: fix throw no error issue when expect a non existed dom 380 | 7. Fix: fix issue when expect a string contain `'` 381 | 382 | ## ver 2.3.27 (2017-2-4) 383 | 384 | 1. Fix: fix chromedriver install failed issue 385 | 2. Fix: fix run failed in windows system 386 | 387 | ## ver 2.3.26 (2017-1-16) 388 | 389 | 1. Add: support single test by run command 390 | 2. Add: support hide dom before expect 391 | 392 | ## ver 2.3.25 (2017-1-12) 393 | 394 | 1. Add: support scroll in element 395 | 396 | ## ver 2.3.24 (2017-1-6) 397 | 398 | 1. Fix: support node v7.x 399 | 2. Update: up chromedriver to v2.27 400 | 401 | ## ver 2.3.23 (2017-1-6) 402 | 403 | 1. Add: check page error after page loaded 404 | 405 | ## ver 2.3.22 (2017-1-4) 406 | 407 | 1. Add: support disable path attr temporary 408 | 2. Fix: fix updatevar failed issue when in iframe 409 | 410 | ## ver 2.3.19 (2016-12-28) 411 | 412 | 1. Fix: exclude uirecorder tool panel doms when get dom path 413 | 2. Fix: fix aria upload role issue 414 | 415 | ## ver 2.3.16 (2016-12-28) 416 | 417 | 1. Fix: fix upload check failed issue 418 | 2. Add: support aria role for upload 419 | 420 | ## ver 2.3.15 (2016-12-27) 421 | 422 | 1. Add: update mochawesome-uirecorder, add support lightbox 423 | 424 | ## ver 2.3.13 (2016-12-24) 425 | 426 | 1. Fix: update jwebdriver to v2.0.5, fix issue: send key to rich editor failed with first time 427 | 428 | ## ver 2.3.12 (2016-12-23) 429 | 430 | 1. Fix: fix double event issue when in rich editor 431 | 432 | ## ver 2.3.11 (2016-12-23) 433 | 434 | 1. Fix: fix click event lost issue in some special case 435 | 436 | ## ver 2.3.10 (2016-12-22) 437 | 438 | 1. Add: support jump to var only url 439 | 440 | ## ver 2.3.9 (2016-12-21) 441 | 442 | 1. Add: support change webdriver host & port by env 443 | 2. Add: change to use chromedriver (https://www.npmjs.com/package/chromedriver) 444 | 445 | ## ver 2.3.8 (2016-12-20) 446 | 447 | 1. Add: support auto check update 448 | 449 | ## ver 2.3.7 (2016-12-20) 450 | 451 | 1. Add: support more expect type: notEqual, above, below, match, notMatch 452 | 2. Add: add readme: How to add expect after hover? 453 | 454 | ## ver 2.3.6 (2016-12-20) 455 | 456 | 1. Fix: fix jwebdriver chai issue when throw error by promise 457 | 458 | ## ver 2.3.5 (2016-12-19) 459 | 460 | 1. Fix: fix mouseUp issue when change window 461 | 462 | ## ver 2.3.4 (2016-12-16) 463 | 464 | 1. Fix: fix mouseDown issue when open new window 465 | 466 | ## ver 2.3.3 (2016-12-16) 467 | 468 | 1. Update: up chromedriver to v2.26 469 | 470 | ## ver 2.3.2 (2016-12-13) 471 | 472 | 1. Add: module dialog change to jump to dialog, support for url jump 473 | 474 | ## ver 2.3.0 (2016-12-12) 475 | 476 | 1. Fix: switchWindow losted when check browser is disabled (PC) 477 | 2. Add: support update var with webdriver (PC) 478 | 2. Add: support define different hosts file for different runtime (PC) 479 | 480 | ## ver 2.2.18 (2016-12-6) 481 | 482 | 1. Fix: exit with code 0 when use mochawesome-uirecorder, support to jenkins 483 | 484 | ## ver 2.2.15 (2016-12-6) 485 | 486 | 1. Fix: support test case saved in third level directory 487 | 488 | ## ver 2.2.14 (2016-12-5) 489 | 490 | 1. Fix: fix mouseUp issue again 491 | 492 | ## ver 2.2.13 (2016-12-5) 493 | 494 | 1. Fix: fix frame id issue 495 | 2. Fix: fix mouseUp issue 496 | 3. Add: support set delay time for expect 497 | 498 | ## ver 2.2.12 (2016-12-2) 499 | 500 | 1. Fix: update jwebdriver to fix findVisbile issue 501 | 2. Fix: fix drag drop issue in some special case 502 | 503 | ## ver 2.2.11 (2016-12-1) 504 | 505 | 1. Add: support record for shadow dom 506 | 507 | ## ver 2.2.10 (2016-12-1) 508 | 509 | 1. Fix: update jwebdriver, fix local ip issue 510 | 511 | ## ver 2.2.9 (2016-11-30) 512 | 513 | 1. Fix: disable flash when recording 514 | 515 | ## ver 2.2.8 (2016-11-30) 516 | 517 | 1. Fix: fix project files for mobile mode 518 | 2. Fix: fix some issues for record tool panel 519 | 520 | ## ver 2.2.7 (2016-11-29) 521 | 522 | 1. Add: support parallel test 523 | 524 | ## ver 2.2.4 (2016-11-29) 525 | 526 | 1. Add: change to local file upload 527 | 528 | ## ver 2.2.0 (2016-11-29) 529 | 530 | 1. Add: show config path for user confirm runtime 531 | 2. Add: init full test project 532 | 3. Add: add tutorial how to dock jenkins 533 | 4. Fix: fix websocket connect failed when system use invalid proxy 534 | 535 | ## ver 2.1.17 (2016-11-25) 536 | 537 | 1. Add: support runtime switch 538 | 539 | ## ver 2.1.16 (2016-11-25) 540 | 541 | 1. Fix: fix var string issue again 542 | 543 | ## ver 2.1.15 (2016-11-24) 544 | 545 | 1. Fix: fix var string no support for boolean type 546 | 547 | ## ver 2.1.14 (2016-11-23) 548 | 549 | 1. Add: add simulate input event when insert var in recording 550 | 2. Add: show common spec lists in start page 551 | 552 | ## ver 2.1.11 (2016-11-18) 553 | 554 | 1. Update: update to chromedriver v2.25 555 | 2. Fix: fix common test load failed issue 556 | 3. Fix: fix show text failed when i18n load slowly 557 | 4. Add: support var edit feature 558 | 559 | ## ver 2.1.10 (2016-11-17) 560 | 561 | 1. Add: create commons directory when init config 562 | 2. Add: add change log link to README 563 | 564 | ## ver 2.1.9 (2016-11-17) 565 | 566 | 1. Add: show version at top 567 | 568 | ## ver 2.1.7 (2016-11-17) 569 | 570 | 1. Fix: fix screenshots filename issue 571 | 572 | ## ver 2.1.5 (2016-11-16) 573 | 574 | 1. Fix: fix add double expect commands issue 575 | 2. Add: add reporter mochawesome-uirecorder, support list screenshots 576 | 577 | ## ver 2.1.4 (2016-11-15) 578 | 579 | 1. Fix: fix root path 580 | 2. Fix: fix readme 581 | 582 | ## ver 2.1.3 (2016-11-15) 583 | 584 | 1. Add: support start with url include var 585 | 2. Add: support expect to string include var 586 | 3. Add: add mochawesome reporter to readme 587 | 588 | ## ver 2.1.2 (2016-11-14) 589 | 590 | 1. Fix: fix dblClick crash issue 591 | 592 | ## ver 2.1.1 (2016-11-14) 593 | 594 | 1. Add: support new expect type: alert 595 | 2. Fix: fix issue for expect type: url, title, cookie, localStorage, sessionStorage 596 | 597 | ## ver 2.1.0 (2016-11-11) 598 | 599 | 1. Add: support save test case into sub directory 600 | 2. Fix: fix issue when url contain space at front or end 601 | 3. Add: create screenshots directory when init config 602 | 603 | ## ver 2.0.3 (2016-11-9) 604 | 605 | 1. Add: save screenshots after each step 606 | 2. Fix: fix bin issue for mac & linux 607 | 3. Fix: fix some issue for pc record 608 | 4. Add: support edit path when expect dom 609 | 610 | ## ver 2.0.0 (2016-11-8) 611 | 612 | 1. Add: Support jWebDriver v2.0.0 613 | 2. Add: Support macaca for mobile record 614 | 615 | ## ver 1.4.0 (2016-9-22) 616 | 617 | 1. Add: add default help to cli 618 | 2. Add: support define browser size for test case 619 | 620 | ## ver 1.3.0 (2016-9-20) 621 | 622 | 1. Add: find visible elements for DOM PATH, short path length, more compatibility 623 | 2. Fix: fix chrome open failed issue when computer is very slow 624 | 3. Update: update to chromedriver v2.24 625 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 - 2017 Alibaba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI Recorder 2 | 3 | --- 4 | 5 | ![logo.png](https://raw.github.com/alibaba/uirecorder/master/logo.png) 6 | 7 | [![NPM version](https://img.shields.io/npm/v/uirecorder.svg)](https://www.npmjs.com/package/uirecorder) 8 | [![License](https://img.shields.io/npm/l/uirecorder.svg)](https://www.npmjs.com/package/uirecorder) 9 | [![NPM count](https://img.shields.io/npm/dm/uirecorder.svg)](https://www.npmjs.com/package/uirecorder) 10 | [![NPM count](https://img.shields.io/npm/dt/uirecorder.svg)](https://www.npmjs.com/package/uirecorder) 11 | [![TesterHome](https://img.shields.io/badge/TTF-TesterHome-2955C5.svg)](https://testerhome.com/github_statistics) 12 | 13 | UI Recorder is multi-platform UI test case recorder like [Selenium IDE](http://docs.seleniumhq.org/projects/ide/) but more powerful than Selenium IDE! 14 | 15 | UI Recorder is easy to use, even zero cost. 16 | 17 | 1. Official Site: [https://www.yuque.com/artist/uirecorder/](https://www.yuque.com/artist/uirecorder/) 18 | 2. Language Switch: [English](https://github.com/alibaba/uirecorder/blob/master/README.md), [中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-cn.md) 19 | 3. Change log: [CHANGE](https://github.com/alibaba/uirecorder/blob/master/CHANGE.md) 20 | 4. Video Tutorial:[PC中文教程](http://v.youku.com/v_show/id_XMTY4NTk5NjI4MA==.html) 21 | 22 | 23 | # Features 24 | 25 | 1. Support all user operation: key event, mouse event, alert, file upload, drag, svg, shadow dom 26 | 2. Support mobile native APP(Android, iOS) recorde, powered by [Macaca](https://macacajs.github.io) 27 | 3. No interference when recording: the same as self test 28 | 4. Record test file saved in local 29 | 5. Support kinds of expect: val,text,displayed,enabled,selected,attr,css,url,title,cookie,localStorage,sessionStorage 30 | 6. Support image diff 31 | 7. Support powerful var string 32 | 8. Support common test case: one case call another 33 | 9. Support parallel test 34 | 10. Support i18n: en, zh-cn, zh-tw 35 | 11. Support screenshots after each step 36 | 12. Support HTML report & JUnit report 37 | 13. Support multi systems: Windows, Mac, Linux 38 | 14. Test file base on NodeJs: [jWebDriver](http://jwebdriver.com/) 39 | 40 | 41 | 42 | # Contributors 43 | 44 | |[
itestauipi](https://github.com/itestauipi)
|[
Stngle](https://github.com/Stngle)
|[
yaniswang](https://github.com/yaniswang)
|[
xudafeng](https://github.com/xudafeng)
|[
undead25](https://github.com/undead25)
|[
stevobm](https://github.com/stevobm)
| 45 | | :---: | :---: | :---: | :---: | :---: | :---: | 46 | |[
micosty](https://github.com/micosty)
|[
ali-lion](https://github.com/ali-lion)
|[
alibaba-oss](https://github.com/alibaba-oss)
|[
felizalde](https://github.com/felizalde)
|[
portokallidis](https://github.com/portokallidis)
|[
snapre](https://github.com/snapre)
| 47 | [
paradite](https://github.com/paradite)
|[
WingOfTime](https://github.com/WingOfTime)
|[
zquancai](https://github.com/zquancai)
48 | 49 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Sat Apr 30 2022 21:11:26 GMT+0800`. 50 | 51 | 52 | 53 | # Screenshots 54 | 55 | ![shot1](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot1.png) 56 | 57 | ![shot2](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot2.png) 58 | 59 | ![shot3](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot3.png) 60 | 61 | ![shot4](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot4.png) 62 | 63 | # Video demo 64 | 65 | 66 | ![video1](http://wx1.sinaimg.cn/mw1024/7f3afc78gy1fdf5gass5rg20sg0g0kjo.gif) 67 | 68 | ![video2](http://wx2.sinaimg.cn/mw1024/7f3afc78gy1fdf5hb8anig20sg0g0u12.gif) 69 | 70 | # Quick start 71 | 72 | 73 | ## Install 74 | 75 | 1. Install NodeJs (version >= v7.x) 76 | 77 | > [https://nodejs.org/](https://nodejs.org/) 78 | 79 | > `sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}` (Mac, Linux) 80 | 81 | 2. Install chrome 82 | 83 | > [https://www.google.com/chrome/](https://www.google.com/chrome/) 84 | 85 | 3. Install UI Recorder 86 | 87 | > `npm install uirecorder mocha -g` 88 | 89 | ## PC record 90 | 91 | 92 | 1. Init test project 93 | 94 | > Create new folder 95 | 96 | > `uirecorder init` 97 | 98 | 2. Start record test case 99 | 100 | > edit hosts file 101 | 102 | > `uirecorder sample/test.spec.js` 103 | 104 | 3. Start WebDriver Server 105 | 106 | 4. Run test case 107 | 108 | > Run all case: `source run.sh` ( Linux|Mac ) or `run.bat` ( Windows ) 109 | 110 | > Run single case: `source run.sh sample/test.spec.js` ( Linux|Mac ) or `run.bat sample/test.spec.js` ( Windows ) 111 | 112 | 5. Get reports & screenshots 113 | 114 | > ./reports/index.html 115 | 116 | > ./reports/index.xml (JUnit) 117 | 118 | > ./reports/index.json 119 | 120 | > ./screenshots/ 121 | 122 | ## More Platform Support 123 | 124 |

125 | 126 | Macaca 131 | 132 |

133 | 134 | 1. Install & start macaca server: 135 | 136 | > Install [Macaca](http://macacajs.github.io) 137 | 138 | > Connect your mobile or open emulator 139 | 140 | > `macaca server --port 4444` 141 | 142 | 2. Init test project 143 | 144 | > Create new folder 145 | 146 | > `uirecorder init --mobile` 147 | 148 | 3. Start record test case 149 | 150 | > `uirecorder --mobile sample/test.spec.js` 151 | 152 | 4. Run test case 153 | 154 | > Run all case: `source run.sh` ( Linux|Mac ) or `run.bat` ( Windows ) 155 | 156 | > Run single case: `source run.sh sample/test.spec.js` ( Linux|Mac ) or `run.bat sample/test.spec.js` ( Windows ) 157 | 158 | 5. Get reports & screenshots 159 | 160 | > ./reports/index.html 161 | 162 | > ./reports/index.xml (JUnit) 163 | 164 | > ./reports/index.json 165 | 166 | > ./screenshots/ 167 | 168 | # Documentation Translations 169 | 170 | 1. [中文使用手册](https://github.com/alibaba/uirecorder/blob/master/doc/zh-cn/readme.md) 171 | 172 | # QA 173 | 174 | ## How to debug test code 175 | 176 | 1. Install [Visual Studio Code](https://code.visualstudio.com/) & open Visual Studio Code 177 | 2. Open the project root folder by vs code 178 | 3. Open test file, add break point 179 | 4. press `F5` key to start, press `F10` key to run next line 180 | 181 | ## How to deploy WebDriver Server 182 | 183 | 1. How to run selenium standalone server? 184 | 185 | > `npm run server` 186 | 187 | 2. Selenium Grid: [https://github.com/SeleniumHQ/selenium/wiki/Grid2](https://github.com/SeleniumHQ/selenium/wiki/Grid2) 188 | 3. F2etest: [https://github.com/alibaba/f2etest](https://github.com/alibaba/f2etest) 189 | 190 | ## How to change webdriver host & port by env temporary, debug for local? 191 | 192 | 1. `export webdriver=127.0.0.1:4444` or `set webdriver=127.0.0.1:4444` (Windows) 193 | 194 | Tip: port is not required, For example: `export webdriver=127.0.0.1` 195 | 196 | ## How to dock Jenkins? 197 | 198 | 1. Add commands 199 | 200 | source ./install.sh 201 | source ./run.sh 202 | 203 | 2. Add reports 204 | 205 | > [JUnit](https://wiki.jenkins-ci.org/display/JENKINS/JUnit+Plugin): `reports/index.xml` 206 | 207 | > [HTML](https://wiki.jenkins-ci.org/display/JENKINS/HTML+Publisher+Plugin): `reports/index.html` 208 | 209 | ## How to filter unstable path 210 | 211 | 1. Because some attribute values are random or unstable, we can't record a stable CSS selector 212 | 2. We can filter the attributes with a blacklist. You can type `uirecorder init` and then input the blacklist from the command line 213 | 214 | Tip: blacklist is a regex, you can use it like this: `/attr_\d+/` 215 | 216 | ## How to record common test case? 217 | 218 | 1. Record `commons/login.mod.js` 219 | 2. Record `sample/test.spec.js` 220 | 221 | 1. please input `login.mod.js` in recorder start page or jump test case in page 222 | 2. After `login.mod.js` loaded, then recorder other steps 223 | 224 | 3. `source run.sh` ( Linux|Mac ) or `run.bat` ( Windows ) 225 | 226 | ## How to record file upload? 227 | 228 | 1. UI Recorder only support native file compont 229 | 2. direct click `` or click ``, the placeholder button must mark as `upload` with `role` or `data-role` 230 | 3. File must save to `uploadfiles/` directory 231 | 232 | ## How to use vars 233 | 234 | edit config.json 235 | 236 | { 237 | "recorder": { 238 | ... 239 | }, 240 | "webdriver": { 241 | ... 242 | }, 243 | "vars": { 244 | "productId": "123456", 245 | "productName": "mp3" 246 | } 247 | } 248 | 249 | 1. start with url: `http://xxx.com/product?id={{productId}}` 250 | 2. add new var with tool panel 251 | 3. update var with tool panel 252 | 4. jump url with tool panel: `http://xxx.com/product?id={{productId}}` 253 | 5. insert vars string with tool panel: `{{productName}}` or `aaa{{productName}}bbb` 254 | 6. expect to var string: `{{productName}}` or `aaa{{productName}}bbb` 255 | 256 | Tip: All var string also support js template string, For example: `{{productName}}, ${new Date().getTime()}, ${parseInt(testVars.a)+parseInt(testVars.b)}` 257 | 258 | 259 | ## How to add hover multiple or add expect after a hover? 260 | 261 | 1. Press down `Ctrl` or `Command` button 262 | 2. Click `Add Hover` Button, enter hover mode 263 | 3. Release `Ctrl` or `Command` button 264 | 4. Click the dom you want to hover (can add multiple) 265 | 5. Click `Add Expect` Button 266 | 6. Click the dom you want to expect 267 | 7. Press `Esc` button or click `End Hover` Button, exit hover mode 268 | 269 | ## How to expect the value after js eval in front browser? 270 | 271 | 1. `Add Expect`, select type `jscode` 272 | 2. sync mode: `return document.title` 273 | 3. function mode: 274 | 275 | function(){ 276 | var str = "aaa"; 277 | return str; 278 | } 279 | 280 | 4. async mode: 281 | 282 | function(done){ 283 | setTimeout(function(){ 284 | done(123); 285 | }, 100); 286 | } 287 | 288 | ## How to hide doms before expect? 289 | 290 | 1. `uirecorder init` 291 | 2. Input css selector when init `Hide before expect` 292 | 3. `uirecorder start` 293 | 4. UIRecorder will hide matched doms before expect, then you can expect the dom behind the mask div 294 | 295 | ## How to record option click? 296 | 297 | Some steps is not very important, but occasionally displayed, this steps will expect to success always. 298 | 299 | 1. Press 'Alt' button 300 | 2. Click the target DOM 301 | 302 | ## How to use image diff? 303 | 304 | 1. Install GraphicsMagick 305 | 306 | > brew install graphicsmagick (Mac) 307 | 308 | > sudo apt-get install graphicsmagick (Linux) 309 | 310 | > http://www.graphicsmagick.org/download.html (Windows) 311 | 312 | 2. Add expect with imgdiff 313 | 314 | > select expect type: imgdiff 315 | 316 | > select target element 317 | 318 | 3. Rebuild the baseline image 319 | 320 | > `source run.sh sample/test.spec.js --rebuilddiff` (Mac | Linux) 321 | 322 | > `run.bat sample/test.spec.js --rebuilddiff` (Windows) 323 | 324 | ## Can't do when recording 325 | 326 | 1. don't change url in location bar 327 | 2. don't change focus by TAB key 328 | 3. don't use dblclick, WebDriver no support 329 | 4. don't select text by mouse, WebDriver no support 330 | 5. don't focus to background window manualy 331 | 6. don't click useless DOM, only record key steps 332 | 333 | ## How develop test friendly code? 334 | 335 | 1. please dont't use random id or name 336 | 2. please name a id for DOM area 337 | 3. add label for form 338 | 4. please listen click event instead of mousedown 339 | 340 | ## How to set udid to mobile test? 341 | 342 | 1. `export devices=xxx1,xxx2` (windows: `set devices=xxx1,xxx2`) 343 | 2. `source run.sh` ( Linux|Mac ) or `run.bat` ( Windows ) 344 | 345 | ## How to save raw cmds json? 346 | 347 | 1. `uirecorder start --raw` 348 | 2. After test saved, then you can get 2 files: `sample/test.spec.js`, `sample/test.spec.json` 349 | 350 | ## Other Tips 351 | 352 | 1. Mac system: localhost must place in hosts 353 | 354 | # License 355 | 356 | UIRecorder is released under the MIT license. 357 | 358 | # Thanks 359 | 360 | * jWebDriver: [https://github.com/yaniswang/jWebDriver](https://github.com/yaniswang/jWebDriver) 361 | * chai: [https://github.com/chaijs/chai](https://github.com/chaijs/chai) 362 | * macaca-mocha-parallel-tests: [https://github.com/macacajs/macaca-mocha-parallel-tests](https://github.com/macacajs/macaca-mocha-parallel-tests) 363 | * macaca-reporter: [https://github.com/macacajs/macaca-reporter](https://github.com/macacajs/macaca-reporter) 364 | -------------------------------------------------------------------------------- /README_zh-cn.md: -------------------------------------------------------------------------------- 1 | # UI Recorder 2 | 3 | --- 4 | 5 | ![logo.png](https://raw.github.com/alibaba/uirecorder/master/logo.png) 6 | 7 | [![NPM version](https://img.shields.io/npm/v/uirecorder.svg?style=flat-square)](https://www.npmjs.com/package/uirecorder) 8 | [![License](https://img.shields.io/npm/l/uirecorder.svg?style=flat-square)](https://www.npmjs.com/package/uirecorder) 9 | [![NPM count](https://img.shields.io/npm/dm/uirecorder.svg?style=flat-square)](https://www.npmjs.com/package/uirecorder) 10 | [![NPM count](https://img.shields.io/npm/dt/uirecorder.svg?style=flat-square)](https://www.npmjs.com/package/uirecorder) 11 | [![TesterHome](https://img.shields.io/badge/TTF-TesterHome-2955C5.svg)](https://testerhome.com/github_statistics) 12 | 13 | UI Recorder 是一款面向多端的 UI 自动化录制工具,类似于[Selenium IDE](http://docs.seleniumhq.org/projects/ide/) 但比Selenium IDE 更加强大! 14 | 15 | UI Recorder 非常简单易用,零成本解决测试回归问题。 16 | 17 | 1. 官方网站: [https://www.yuque.com/artist/uirecorder](https://www.yuque.com/artist/uirecorder/) 18 | 2. 语言切换: [English](https://github.com/alibaba/uirecorder/blob/master/README.md), [中文](https://github.com/alibaba/uirecorder/blob/master/README_zh-cn.md) 19 | 3. 变更日志: [CHANGE](https://github.com/alibaba/uirecorder/blob/master/CHANGE.md) 20 | 4. 视频教程:[PC中文教程](http://v.youku.com/v_show/id_XMTY4NTk5NjI4MA==.html) 21 | 5. 钉钉交流群:30684524(加入验证:UIRecorder录制),下载钉钉:[https://www.dingtalk.com/](https://www.dingtalk.com/) 22 | 6. 最新中文手册:[语雀文档](https://www.yuque.com/artist/uirecorder)、[Github Page](https://alibaba.github.io/uirecorder/build/#/artist/uirecorder/hbqzpl) 23 | 24 | # 功能 25 | 26 | 1. 支持所有用户行为: 键盘事件, 鼠标事件, alert, 文件上传, 拖放, svg, shadow dom 27 | 2. 全平台支持,移动端 Android, iOS 录制, 基于 [Macaca](https://macacajs.github.io) 实现 28 | 3. 无干扰录制: 和正常测试无任何区别,无需任何交互 29 | 4. 录制用例存储在本地 30 | 5. 支持丰富的断言类型: val,text,displayed,enabled,selected,attr,css,url,title,cookie,localStorage,sessionStorage 31 | 6. 支持图片对比 32 | 7. 支持强大的变量字符串 33 | 8. 支持公共测试用例: 允许用例中动态调用另外一个 34 | 9. 支持并发测试 35 | 10. 支持多国语言: 英文, 简体中文, 繁体中文 36 | 11. 支持单步截图 37 | 12. 支持HTML报告和JUnit报告 38 | 13. 全系统支持: Windows, Mac, Linux 39 | 14. 基于Nodejs的测试用例: [jWebDriver](http://jwebdriver.com/) 40 | 41 | # 软件截图 42 | 43 | ![shot1](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot1.png) 44 | 45 | ![shot2](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot2.png) 46 | 47 | ![shot3](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot3.png) 48 | 49 | ![shot4](https://raw.github.com/alibaba/uirecorder/master/screenshot/shot4.png) 50 | 51 | # 视频演示 52 | 53 | ![video1](http://wx1.sinaimg.cn/mw1024/7f3afc78gy1fdf5gass5rg20sg0g0kjo.gif) 54 | 55 | ![video2](http://wx2.sinaimg.cn/mw1024/7f3afc78gy1fdf5hb8anig20sg0g0u12.gif) 56 | 57 | # 快速开始 58 | 59 | ## 安装 60 | 61 | 1. 安装 NodeJs (版本号 >= v7.x) 62 | 63 | > [https://nodejs.org/](https://nodejs.org/) 64 | 65 | > `sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}` (Mac, Linux) 66 | 67 | 2. 安装 chrome 68 | 69 | > [https://www.google.com/chrome/](https://www.google.com/chrome/) 70 | 71 | 3. 安装 UI Recorder 72 | 73 | > `npm install uirecorder mocha -g` 74 | 75 | ## PC录制 76 | 77 | 1. 初始化测试工程 78 | 79 | > 创建新文件夹 80 | 81 | > `uirecorder init` 82 | 83 | 2. 开始录制测试用例 84 | 85 | > 修改hosts文件 86 | 87 | > `uirecorder sample/test.spec.js` 88 | 89 | 3. 启动WebDriver服务器 90 | 91 | 4. 运行测试用例 92 | 93 | > 运行所有脚本: `source run.sh` ( Linux|Mac ) 或 `run.bat` ( Windows ) 94 | 95 | > 运行单个脚本: `source run.sh sample/test.spec.js` ( Linux|Mac ) 或 `run.bat sample/test.spec.js` ( Windows ) 96 | 97 | 5. 获得测试报告和单步截图 98 | 99 | > ./reports/index.html 100 | 101 | > ./reports/index.xml (JUnit) 102 | 103 | > ./reports/index.json 104 | 105 | > ./screenshots/ 106 | 107 | ## 更多平台 108 | 109 |

110 | 111 | Macaca 116 | 117 |

118 | 119 | 1. 安装并且启动macaca server: 120 | 121 | > 安装 [Macaca](http://macacajs.github.io) 122 | 123 | > 连接你的手机或模拟器 124 | 125 | > `macaca server --port 4444` 126 | 127 | 2. 初始化测试工程 128 | 129 | > 创建新文件夹 130 | 131 | > `uirecorder init --mobile` 132 | 133 | 3. 开始录制测试用例 134 | 135 | > `uirecorder --mobile sample/test.spec.js` 136 | 137 | 4. 运行测试用例 138 | 139 | > 运行所有脚本: `source run.sh` ( Linux|Mac ) 或 `run.bat` ( Windows ) 140 | 141 | > 运行单个脚本: `source run.sh sample/test.spec.js` ( Linux|Mac ) 或 `run.bat sample/test.spec.js` ( Windows ) 142 | 143 | 5. 获得测试报告和单步截图 144 | 145 | > ./reports/index.html 146 | 147 | > ./reports/index.xml (JUnit) 148 | 149 | > ./reports/index.json 150 | 151 | > ./screenshots/ 152 | 153 | # 文档翻译 154 | 155 | 1. [中文使用手册](/doc/zh-cn/readme.md) 156 | 157 | 158 | # License 159 | 160 | UIRecorder is released under the MIT license. 161 | 162 | # 感谢 163 | 164 | * jWebDriver: [https://github.com/yaniswang/jWebDriver](https://github.com/yaniswang/jWebDriver) 165 | * chai: [https://github.com/chaijs/chai](https://github.com/chaijs/chai) 166 | * macaca-mocha-parallel-tests: [https://github.com/macacajs/macaca-mocha-parallel-tests](https://github.com/macacajs/macaca-mocha-parallel-tests) 167 | * macaca-reporter: [https://github.com/macacajs/macaca-reporter](https://github.com/macacajs/macaca-reporter) 168 | -------------------------------------------------------------------------------- /bin/uirecorder: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var program = require('commander'); 6 | var UIRecorder = require('../'); 7 | 8 | var pkg = require('../package.json'); 9 | 10 | function map(val) { 11 | var objMap = {}; 12 | val.split(',').forEach(function(item){ 13 | var arrItem = item.split(/\s*=\s*/); 14 | objMap[arrItem[0]] = arrItem[1]?arrItem[1]:true; 15 | }); 16 | return objMap; 17 | } 18 | 19 | // stdout log 20 | var rawStdoutWrite = process.stdout.write; 21 | fs.writeFileSync('uirecorder.log', '') 22 | process.stdout.write = function(string, encoding, fileDescriptor) { 23 | var nocolor = string.replace(/(\[\d+[mdc]|\[K)/gi, ''); 24 | fs.appendFileSync('uirecorder.log', nocolor) 25 | rawStdoutWrite.apply(process.stdout, arguments); 26 | } 27 | 28 | console.log((" __ ______ ____ __ \n\ 29 | / / / / _/ / __ \\___ _________ _________/ /__ _____\n\ 30 | / / / // / / /_/ / _ \\/ ___/ __ \\/ ___/ __ / _ \\/ ___/\n\ 31 | / /_/ // / / _, _/ __/ /__/ /_/ / / / /_/ / __/ / \n\ 32 | \\____/___/ /_/ |_|\\___/\\___/\\____/_/ \\__,_/\\___/_/ v"+pkg.version).green); 33 | console.log(''); 34 | console.log('Official Site: http://uirecorder.com'.cyan); 35 | console.log('------------------------------------------------------------------\n'.green); 36 | 37 | program 38 | .version(pkg.version) 39 | .option('-l, --lang ', 'change language') 40 | .option('--no-color', 'disable colors') 41 | .option('-m --mobile', 'mobile mode') 42 | .option('-d --debug', 'debug mode') 43 | .option('-r --raw', 'save raw cmds') 44 | .option('--default', 'open checker browser and set 1024x768 by default') 45 | .option('--device [value]', 'set mobile device name'); 46 | 47 | var cmd = null; 48 | 49 | program.command('init') 50 | .description('Init UIRecorder config file') 51 | .action(function(){ 52 | cmd = 'init'; 53 | UIRecorder.init({ 54 | locale: program.lang, 55 | mobile: program.mobile, 56 | debug: program.debug 57 | }); 58 | }); 59 | 60 | program.command('start') 61 | .description('Start recorder') 62 | .action(function(){ 63 | cmd = 'start' 64 | UIRecorder.start({ 65 | locale: program.lang, 66 | cmdFilename: program.args.length === 2 ? program.args[0] : '', 67 | mobile: program.mobile, 68 | debug: program.debug, 69 | raw: program.raw, 70 | default: program.default, 71 | mobileDevice: program.device 72 | }); 73 | }); 74 | 75 | program.parse(process.argv); 76 | UIRecorder.checkUpdate(program.lang); 77 | 78 | if(cmd === null){ 79 | // default command 80 | var rootPath = getRootPath(); 81 | 82 | var configPath = 'config.json'; 83 | var configFile = path.resolve(rootPath + '/' + configPath); 84 | var configJson = {}; 85 | 86 | if(fs.existsSync(configFile)){ 87 | UIRecorder.start({ 88 | locale: program.lang, 89 | cmdFilename: program.args.length === 1 ? program.args[0] : '', 90 | mobile: program.mobile, 91 | debug: program.debug, 92 | raw: program.raw, 93 | default: program.default, 94 | mobileDevice: program.device 95 | }); 96 | } 97 | else{ 98 | console.log(configPath.bold+' '+__('file_missed').red); 99 | console.log(''); 100 | UIRecorder.init({ 101 | locale: program.lang, 102 | mobile: program.mobile, 103 | debug: program.debug 104 | }); 105 | } 106 | 107 | } 108 | 109 | // get test root 110 | function getRootPath(){ 111 | var rootPath = path.resolve('.'); 112 | while(rootPath){ 113 | if(fs.existsSync(rootPath + '/config.json')){ 114 | break; 115 | } 116 | rootPath = rootPath.substring(0, rootPath.lastIndexOf(path.sep)); 117 | } 118 | return rootPath; 119 | } 120 | -------------------------------------------------------------------------------- /buildcrx.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --pack-extension=%cd%\chrome-extension --pack-extension-key=%cd%\uirecorder.pem 3 | move chrome-extension.crx tool/uirecorder.crx 4 | echo Build done! 5 | -------------------------------------------------------------------------------- /buildcrx.sh: -------------------------------------------------------------------------------- 1 | /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --pack-extension=./chrome-extension --pack-extension-key=./uirecorder.pem 2 | mv chrome-extension.crx tool/uirecorder.crx 3 | echo Build done! 4 | -------------------------------------------------------------------------------- /chrome-extension/img/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/alert.png -------------------------------------------------------------------------------- /chrome-extension/img/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/back.png -------------------------------------------------------------------------------- /chrome-extension/img/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/cancel.png -------------------------------------------------------------------------------- /chrome-extension/img/end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/end.png -------------------------------------------------------------------------------- /chrome-extension/img/expect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/expect.png -------------------------------------------------------------------------------- /chrome-extension/img/fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/fail.png -------------------------------------------------------------------------------- /chrome-extension/img/hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/hover.png -------------------------------------------------------------------------------- /chrome-extension/img/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/icon-128.png -------------------------------------------------------------------------------- /chrome-extension/img/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/icon-64.png -------------------------------------------------------------------------------- /chrome-extension/img/icon-disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/icon-disable.png -------------------------------------------------------------------------------- /chrome-extension/img/icon-record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/icon-record.png -------------------------------------------------------------------------------- /chrome-extension/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/icon.png -------------------------------------------------------------------------------- /chrome-extension/img/jscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/jscode.png -------------------------------------------------------------------------------- /chrome-extension/img/jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/jump.png -------------------------------------------------------------------------------- /chrome-extension/img/mloading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/mloading.gif -------------------------------------------------------------------------------- /chrome-extension/img/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/ok.png -------------------------------------------------------------------------------- /chrome-extension/img/sleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/sleep.png -------------------------------------------------------------------------------- /chrome-extension/img/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/success.png -------------------------------------------------------------------------------- /chrome-extension/img/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/text.png -------------------------------------------------------------------------------- /chrome-extension/img/vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/vars.png -------------------------------------------------------------------------------- /chrome-extension/img/warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/chrome-extension/img/warn.png -------------------------------------------------------------------------------- /chrome-extension/js/background.js: -------------------------------------------------------------------------------- 1 | var ENABLE_ICON1 = 'img/icon.png'; 2 | var ENABLE_ICON2 = 'img/icon-record.png'; 3 | var DISABLE_ICON = 'img/icon-disable.png'; 4 | 5 | var isWorking = true; 6 | var workIcon = 1; 7 | var workIconTimer = null; 8 | var recordConfig = null; 9 | var isModuleLoading = false; 10 | 11 | // i18n 12 | var i18n = {}; 13 | var __ = function(str){ 14 | var args = arguments; 15 | str = i18n[str] || str; 16 | var count = 0; 17 | str = str.replace(/%s/g, function(){ 18 | count ++; 19 | return args[count] || ''; 20 | }); 21 | return str; 22 | }; 23 | 24 | // Global events port 25 | var mapGlobalEvents = {}; 26 | var GlobalEvents = { 27 | on: function(type, handler){ 28 | var arrEvents = mapGlobalEvents[type] || []; 29 | arrEvents.push(handler); 30 | mapGlobalEvents[type] = arrEvents; 31 | }, 32 | emit: function(type, data){ 33 | sendGlobalEvents({ 34 | type: type, 35 | data: data 36 | }); 37 | }, 38 | _emit: function(type, data){ 39 | var arrEvents = mapGlobalEvents[type] || []; 40 | arrEvents.forEach(function(handler){ 41 | handler(data); 42 | }); 43 | } 44 | }; 45 | var mapPorts = {}; 46 | var maxPortId = 0; 47 | chrome.extension.onConnect.addListener(function(port) { 48 | var portId = maxPortId++; 49 | mapPorts[portId] = port; 50 | var tabId = port.sender.tab.id; 51 | port.onMessage.addListener(function(msg){ 52 | sendGlobalEvents(msg, tabId); 53 | }); 54 | port.onDisconnect.addListener(function(port){ 55 | delete mapPorts[portId]; 56 | }); 57 | }); 58 | function sendGlobalEvents(msg, senderTabId){ 59 | GlobalEvents._emit(msg.type, msg.data); 60 | var port, tabId; 61 | for(var portId in mapPorts){ 62 | port = mapPorts[portId]; 63 | tabId = port.sender.tab.id; 64 | if(senderTabId !== undefined && senderTabId !== tabId){ 65 | port = null; 66 | } 67 | port && port.postMessage(msg); 68 | } 69 | } 70 | 71 | // websocket to ui recorder server 72 | var wsSocket; 73 | function connectServer(port){ 74 | if(!wsSocket){ 75 | wsSocket = new WebSocket('ws://127.0.0.1:'+port, "protocolOne"); 76 | wsSocket.onopen = function (event) { 77 | console.log('ws connected!'); 78 | } 79 | wsSocket.onmessage = function (message) { 80 | message = message.data; 81 | try{ 82 | message = JSON.parse(message); 83 | } 84 | catch(e){} 85 | var type = message.type; 86 | var data = message.data; 87 | switch(type){ 88 | case 'config': 89 | recordConfig = data; 90 | i18n = recordConfig.i18n; 91 | GlobalEvents.emit('updateConfig', recordConfig); 92 | break; 93 | case 'checkResult': 94 | chrome.notifications.create({ 95 | type: 'basic', 96 | iconUrl: 'img/'+(data.success?'success':'fail')+'.png', 97 | title: data.success?__('exec_succeed'):__('exec_failed'), 98 | message: data.title 99 | }); 100 | GlobalEvents.emit('checkResult', data); 101 | break; 102 | case 'moduleStart': 103 | isModuleLoading = true; 104 | recordConfig.isModuleLoading = true; 105 | chrome.notifications.create({ 106 | type: 'basic', 107 | iconUrl: 'img/warn.png', 108 | title: __('module_start_title'), 109 | message: __('module_start_message', data.file) 110 | }); 111 | GlobalEvents.emit('moduleStart'); 112 | break; 113 | case 'moduleEnd': 114 | isModuleLoading = false; 115 | recordConfig.isModuleLoading = false; 116 | chrome.notifications.create({ 117 | type: 'basic', 118 | iconUrl: 'img/'+(data.success?'success':'fail')+'.png', 119 | title: __('module_end_title'), 120 | message: __('module_end_message', data.success?__('succeed'):__('failed'), data.file) 121 | }); 122 | GlobalEvents.emit('moduleEnd'); 123 | break; 124 | case 'mobileAppInfo': 125 | GlobalEvents.emit('mobileAppInfo', data); 126 | break; 127 | 128 | } 129 | } 130 | wsSocket.onclose = function(){ 131 | wsSocket = null; 132 | console.log('ws closed!'); 133 | } 134 | } 135 | } 136 | 137 | GlobalEvents.on('updatePathAttr', function(newAttr){ 138 | var arrPathAttrs = recordConfig.pathAttrs; 139 | arrPathAttrs.forEach(function(attr){ 140 | if(attr.name === newAttr.name){ 141 | attr.on = newAttr.on; 142 | } 143 | }); 144 | }); 145 | 146 | function sendWsMessage(type, data){ 147 | if(wsSocket){ 148 | var message = { 149 | type: type, 150 | data: data 151 | }; 152 | wsSocket.send(JSON.stringify(message)); 153 | } 154 | } 155 | 156 | // set recorder work status 157 | function setRecorderWork(enable){ 158 | isWorking = enable; 159 | if(isWorking){ 160 | chrome.browserAction.setTitle({title: __('icon_record_tip')}); 161 | chrome.browserAction.setIcon({path: workIcon===1?ENABLE_ICON1:ENABLE_ICON2}); 162 | workIcon *= -1; 163 | workIconTimer = setTimeout(function(){ 164 | setRecorderWork(true); 165 | }, 1000); 166 | } 167 | else{ 168 | clearTimeout(workIconTimer); 169 | chrome.browserAction.setTitle({title: __('icon_end_tip')}); 170 | chrome.browserAction.setIcon({path: DISABLE_ICON}); 171 | } 172 | } 173 | 174 | var arrTasks = []; 175 | var lastWindow = -1; 176 | var allKeyMap = {}; 177 | var allMouseMap = {}; 178 | 179 | // save recoreded command 180 | function saveCommand(windowId, frame, cmd, data){ 181 | if(isModuleLoading){ 182 | return; 183 | } 184 | var cmdInfo = { 185 | window: windowId, 186 | frame: frame, 187 | cmd: cmd, 188 | data: data, 189 | fix: false 190 | }; 191 | 192 | checkLostKey(windowId); 193 | 194 | switch(cmd){ 195 | case 'keyDown': 196 | allKeyMap[data.character] = cmdInfo; 197 | break; 198 | case 'keyUp': 199 | delete allKeyMap[data.character]; 200 | break; 201 | case 'mouseDown': 202 | allMouseMap[data.button] = cmdInfo; 203 | break; 204 | case 'mouseUp': 205 | delete allMouseMap[data.button]; 206 | break; 207 | } 208 | 209 | execNextCommand(cmdInfo); 210 | } 211 | 212 | // 补足丢失的事件 213 | function checkLostKey(windowId){ 214 | if(windowId !== lastWindow){ 215 | if(lastWindow !== -1){ 216 | var cmdInfo; 217 | for(var key in allKeyMap){ 218 | cmdInfo = allKeyMap[key]; 219 | execNextCommand({ 220 | window: cmdInfo.window, 221 | frame: cmdInfo.frame, 222 | cmd: 'keyUp', 223 | data: cmdInfo.data, 224 | fix: true 225 | }); 226 | } 227 | allKeyMap = {}; 228 | for(var button in allMouseMap){ 229 | cmdInfo = allMouseMap[button]; 230 | execNextCommand({ 231 | window: cmdInfo.window, 232 | frame: cmdInfo.frame, 233 | cmd: 'mouseUp', 234 | data: cmdInfo.data, 235 | fix: true 236 | }); 237 | } 238 | allMouseMap = {}; 239 | } 240 | lastWindow = windowId; 241 | } 242 | } 243 | 244 | var isRunning = false; 245 | function execNextCommand(newCmdInfo){ 246 | if(newCmdInfo){ 247 | arrTasks.push(newCmdInfo); 248 | } 249 | if(arrTasks.length > 0 && isRunning === false){ 250 | var cmdInfo = arrTasks.shift(); 251 | console.log('cmd: { window: '+cmdInfo.window+', frame: '+cmdInfo.frame+', cmd: '+cmdInfo.cmd+ ', data:', JSON.stringify(cmdInfo.data) + ', fix: '+cmdInfo.fix+' }'); 252 | isRunning = true; 253 | sendWsMessage('saveCmd', cmdInfo); 254 | setTimeout(function(){ 255 | isRunning = false; 256 | execNextCommand(); 257 | }, 200); 258 | } 259 | } 260 | 261 | // manage window id 262 | var arrWindows = []; 263 | function getWindowId(tabId){ 264 | for(var i=0,len=arrWindows.length;i'+__('button_text_text')+'', 83 | (mobilePlatform === 'Android'?''+__('button_back_text')+'':''), 84 | ''+__('button_alert_text')+'', 85 | ''+__('button_expect_text')+'', 86 | ''+__('button_sleep_text')+'', 87 | ''+__('button_jump_text')+'', 88 | ''+__('button_end_text')+'' 89 | ]; 90 | divToolsPannel.innerHTML = arrToolsHtml.join(''); 91 | divDialogBottom.innerHTML = ''+__('dialog_ok')+''+__('dialog_cancel')+''; 92 | } 93 | 94 | divToolsPannel.addEventListener('click', function(event){ 95 | var target = event.target; 96 | if(target.tagName === 'IMG'){ 97 | target = target.parentNode; 98 | } 99 | isSelectorMode = false; 100 | var name = target.name; 101 | switch(name){ 102 | case 'text': 103 | showTextDailog(); 104 | break; 105 | case 'back': 106 | saveCommand('back'); 107 | break; 108 | case 'alert': 109 | showAlertDailog(); 110 | break; 111 | case 'expect': 112 | isSelectorMode = true; 113 | break; 114 | case 'sleep': 115 | showSleepDailog(); 116 | break; 117 | case 'jump': 118 | showJumpDailog(); 119 | break; 120 | case 'end': 121 | chrome.runtime.sendMessage({ 122 | type: 'save' 123 | }); 124 | break; 125 | } 126 | }); 127 | 128 | // 对话框 129 | var okCallback = null; 130 | var cancelCallback = null; 131 | divDomDialog.addEventListener('click', function(event){ 132 | event.stopPropagation(); 133 | event.preventDefault(); 134 | var target = event.target; 135 | if(target.tagName === 'IMG'){ 136 | target = target.parentNode; 137 | } 138 | var name = target.name; 139 | switch(name){ 140 | case 'uirecorder-ok': 141 | okCallback(); 142 | break; 143 | case 'uirecorder-cancel': 144 | hideDialog(); 145 | cancelCallback && cancelCallback(); 146 | break; 147 | } 148 | }); 149 | divDomDialog.addEventListener('keydown', function(event){ 150 | var keyCode = event.keyCode; 151 | if(keyCode === 13 && (event.ctrlKey || event.metaKey)){ 152 | okCallback(); 153 | } 154 | }); 155 | document.addEventListener('keyup', function(event){ 156 | var keyCode = event.keyCode; 157 | switch(keyCode){ 158 | case 27: 159 | if(isShowDialog){ 160 | hideDialog(); 161 | cancelCallback && cancelCallback(); 162 | } 163 | isSelectorMode = false; 164 | break; 165 | } 166 | }); 167 | // 显示对话框 168 | function showDialog(title, content, events){ 169 | domDialogTitle.innerHTML = title; 170 | domDialogContent.innerHTML = content; 171 | okCallback = events.onOk; 172 | cancelCallback = events.onCancel; 173 | divDomDialog.style.display = 'block'; 174 | divDomDialogMask.style.display = 'block'; 175 | isShowDialog = true; 176 | var onInit = events.onInit; 177 | if(onInit){ 178 | onInit(); 179 | } 180 | setDialogCenter(); 181 | } 182 | function setDialogCenter(){ 183 | divDomDialog.style.marginTop = '-'+(divDomDialog.offsetHeight/2)+'px'; 184 | divDomDialog.style.marginLeft = '-'+(divDomDialog.offsetWidth/2)+'px'; 185 | } 186 | // 隐藏对话框 187 | function hideDialog(){ 188 | domDialogTitle.innerHTML = ''; 189 | domDialogContent.innerHTML = ''; 190 | divDomDialog.style.display = 'none'; 191 | divDomDialogMask.style.display = 'none'; 192 | isShowDialog = false; 193 | isSelectorMode = false; 194 | } 195 | 196 | // 延迟对话框 197 | function showSleepDailog(){ 198 | var strHtml = ''; 199 | var domSleepTime; 200 | showDialog(__('dialog_sleep_title'), strHtml, { 201 | onInit: function(){ 202 | domSleepTime = document.getElementById('uirecorder-sleeptime'); 203 | domSleepTime.select(); 204 | domSleepTime.focus(); 205 | }, 206 | onOk: function(){ 207 | var time = domSleepTime.value; 208 | if(/\d+/.test(time)){ 209 | saveCommand('sleep', time); 210 | hideDialog(); 211 | } 212 | else{ 213 | domSleepTime.focus(); 214 | alert(__('dialog_sleep_time_tip')); 215 | } 216 | } 217 | }); 218 | } 219 | 220 | // 跳转对话框 221 | function showJumpDailog(){ 222 | var arrHtmls = [''); 227 | var domTarget; 228 | showDialog(__('dialog_jump_title'), arrHtmls.join(''), { 229 | onInit: function(){ 230 | domTarget = document.getElementById('uirecorder-target'); 231 | domTarget.select(); 232 | domTarget.focus(); 233 | }, 234 | onOk: function(){ 235 | var target = domTarget.value; 236 | if(target){ 237 | saveCommand('module', target); 238 | hideDialog(); 239 | } 240 | else{ 241 | domTarget.focus(); 242 | } 243 | } 244 | }); 245 | } 246 | 247 | // 文字对话框 248 | function showTextDailog(text){ 249 | var strHtml = ''; 250 | var domTextContent; 251 | showDialog(__('dialog_text_title'), strHtml, { 252 | onInit: function(){ 253 | domTextContent = document.getElementById('textContent'); 254 | domTextContent.select(); 255 | domTextContent.focus(); 256 | domTextContent.value = text || ''; 257 | }, 258 | onOk: function(){ 259 | var text = domTextContent.value; 260 | if(text){ 261 | try{ 262 | eval('\`'+text+'\`'); 263 | } 264 | catch(e){ 265 | return alert(e); 266 | } 267 | saveCommand('sendKeys', text+(mobilePlatform === 'iOS' ? '\n' : '{ESCAPE}')); 268 | hideDialog(); 269 | } 270 | else{ 271 | domTextContent.focus(); 272 | alert(__('dialog_text_content_tip')); 273 | } 274 | } 275 | }); 276 | } 277 | 278 | // 文字对话框 279 | function showAlertDailog(){ 280 | var strHtml = ''; 281 | var domAlertOption; 282 | showDialog(__('dialog_alert_title'), strHtml, { 283 | onInit: function(){ 284 | domAlertOption = document.getElementById('alertOption'); 285 | }, 286 | onOk: function(){ 287 | var alertOption = domAlertOption.value; 288 | saveCommand(alertOption+'Alert'); 289 | hideDialog(); 290 | } 291 | }); 292 | } 293 | 294 | // 断言对话框 295 | function showExpectDailog(path){ 296 | var arrHtmls = [ 297 | '' 304 | ]; 305 | var domExpectSleep, domExpectType, domExpectPath, domExpectCompare, domExpectTo; 306 | showDialog(__('dialog_expect_title'), arrHtmls.join(''), { 307 | onInit: function(){ 308 | domExpectSleep = document.getElementById('expectSleep'); 309 | domExpectType = document.getElementById('expectType'); 310 | domExpectPath = document.getElementById('expectPath'); 311 | domExpectCompare = document.getElementById('expectCompare'); 312 | domExpectTo = document.getElementById('expectTo'); 313 | 314 | domExpectSleep.value = '300'; 315 | domExpectPath.value = path; 316 | domExpectTo.focus(); 317 | domExpectType.onchange = function(){ 318 | var type = domExpectType.value; 319 | switch(type){ 320 | case 'imgdiff': 321 | domExpectCompare.value = 'below'; 322 | domExpectTo.value = 5; 323 | break; 324 | } 325 | }; 326 | }, 327 | onOk: function(){ 328 | var sleep = domExpectSleep.value; 329 | var type = domExpectType.value; 330 | var path = domExpectPath.value; 331 | var compare = domExpectCompare.value; 332 | var to = domExpectTo.value; 333 | try{ 334 | switch(compare){ 335 | case 'equal': 336 | case 'notEqual': 337 | /^(true|false)$/.test(to)?eval(to):eval('\`'+to+'\`'); 338 | break; 339 | case 'match': 340 | case 'notMatch': 341 | eval(to); 342 | break; 343 | default: 344 | eval('\`'+to+'\`'); 345 | break; 346 | } 347 | } 348 | catch(e){ 349 | return alert(e); 350 | } 351 | saveCommand('expect', { 352 | sleep: sleep, 353 | type: type, 354 | path: path, 355 | compare: compare, 356 | to: to 357 | }); 358 | hideDialog(); 359 | } 360 | }); 361 | } 362 | 363 | // get event 364 | var appSource = null; 365 | var appTree = null; 366 | var appWidth = 0, appHeight = 0; 367 | var imgWidth = 0, imgHeight = 0; 368 | var checkResult = true; 369 | var scaleX = 1, scaleY =1; 370 | var mapNodeValueCount = {}; 371 | var arrKeyAttrs = ['resource-id', 'name', 'text']; 372 | function saveCommand(cmd, data){ 373 | var cmdData = { 374 | cmd: cmd, 375 | data: data 376 | }; 377 | checkResult = null; 378 | setTimeout(function(){ 379 | if(checkResult === null){ 380 | showLoading(); 381 | } 382 | }, 500); 383 | hideLine(); 384 | chrome.runtime.sendMessage({ 385 | type: 'command', 386 | data: cmdData 387 | }); 388 | } 389 | function scanAllNode(){ 390 | mapNodeValueCount = {}; 391 | scanNode(appTree) 392 | } 393 | function scanNode(node){ 394 | arrKeyAttrs.forEach(function(name){ 395 | var value = node[name]; 396 | if(value){ 397 | var mapCount = mapNodeValueCount[name] || {}; 398 | mapCount[value] = mapCount[value] && mapCount[value] + 1 || 1; 399 | mapNodeValueCount[name] = mapCount; 400 | } 401 | }) 402 | node.class = node.class || ('XCUIElementType' + node.type); 403 | var bounds = node.bounds || ''; 404 | if(bounds){ 405 | var match = bounds.match(/^\[([\d\.]+),([\d\.]+)\]\[([\d\.]+),([\d\.]+)\]$/); 406 | if(match){ 407 | node.startX = parseInt(match[1], 10); 408 | node.startY = parseInt(match[2], 10); 409 | node.endX = parseInt(match[3], 10); 410 | node.endY = parseInt(match[4], 10); 411 | 412 | } 413 | match = bounds.match(/\{([\d\.]+),\s*([\d\.]+)\},\s*\{([\d\.]+),\s*([\d\.]+)\}/); 414 | if(match){ 415 | node.startX = parseInt(match[1], 10); 416 | node.startY = parseInt(match[2], 10); 417 | node.endX = node.startX + parseInt(match[3], 10); 418 | node.endY = node.startY + parseInt(match[4], 10); 419 | } 420 | node.boundSize = (node.endX - node.startX) * (node.endY - node.startY); 421 | } 422 | else if(node.rect){ 423 | var rect = node.rect; 424 | node.startX = rect.x; 425 | node.startY = rect.y; 426 | node.endX = rect.x + rect.width; 427 | node.endY = rect.y + rect.height; 428 | node.boundSize = (node.endX - node.startX) * (node.endY - node.startY); 429 | } 430 | var childNodes = node.children || node.node; 431 | if(childNodes){ 432 | node.children = childNodes; 433 | if(!Array.isArray(childNodes)){ 434 | childNodes = [childNodes]; 435 | } 436 | var childNode; 437 | for(var i=0;i 20 ? leftstr(text, 20) + '...' : text; 460 | nodeInfo.text = text; 461 | } 462 | nodeInfo.path = getNodeXPath(bestNode); 463 | } 464 | else{ 465 | nodeInfo.x = x; 466 | nodeInfo.y = y; 467 | } 468 | return nodeInfo; 469 | } 470 | function getBestNode(node, x, y, bestNodeInfo){ 471 | if(node.boundSize && x >= node.startX && x <= node.endX && y >= node.startY && y <= node.endY || node.boundSize === undefined){ 472 | var childNodes = node.children; 473 | if(childNodes){ 474 | if(!Array.isArray(childNodes)){ 475 | childNodes = [childNodes]; 476 | } 477 | for(var i=0;i 1 ? '['+index+']' : '') + XPath; 503 | node = node.parentNode; 504 | } 505 | return '/'+XPath; 506 | } 507 | function getNodeClassIndex(node){ 508 | var index = 0; 509 | var className = node.class; 510 | var parentNode = node.parentNode; 511 | if(className && parentNode && Array.isArray(parentNode.children) && parentNode.children.length > 1){ 512 | var childNodes = parentNode.children, childNode; 513 | index = -1; 514 | for(var i=0;i 1000 ? 3 : 2; 582 | scaleX /= rate; 583 | scaleY /= rate; 584 | } 585 | checkResult !== null && hideLoading(); 586 | }); 587 | GlobalEvents.on('checkResult', function(data){ 588 | checkResult = data.success || false; 589 | }); 590 | GlobalEvents.on('moduleEnd', function(){ 591 | checkResult = true; 592 | }); 593 | var downX = -9999, downY = -9999, downTime = 0; 594 | screenshot.addEventListener('click', function(event){ 595 | var upX = event.offsetX, upY = event.offsetY; 596 | var clickDuration = new Date().getTime() - downTime; 597 | if(Math.abs(downX - upX) < 20 && Math.abs(downY - upY) < 20){ 598 | var cmdData = getNodeInfo(Math.floor(upX * scaleX), Math.floor(upY * scaleY)); 599 | if(isSelectorMode){ 600 | if(cmdData.path){ 601 | showExpectDailog(cmdData.path); 602 | } 603 | } 604 | else{ 605 | var pressTime = new Date().getTime() - downTime; 606 | cmdData.duration = (pressTime / 1000).toFixed(2); 607 | saveCommand(clickDuration>500?'press':'click', cmdData); 608 | } 609 | downTime = 0; 610 | } 611 | }); 612 | screenshot.addEventListener('mousedown', function(event){ 613 | downX = event.offsetX; 614 | downY = event.offsetY; 615 | downTime = new Date().getTime(); 616 | event.stopPropagation(); 617 | event.preventDefault(); 618 | }); 619 | screenshot.addEventListener('mouseup', function(event){ 620 | var upX = event.offsetX, upY = event.offsetY; 621 | if(downX >=0 && downY >= 0 && 622 | upX >= 0 && upY >= 0 && 623 | (Math.abs(downX - upX) >= 20 || Math.abs(downY - upY) >= 20)){ 624 | var dragTime = new Date().getTime() - downTime; 625 | saveCommand('drag', { 626 | fromX: Math.floor(downX * scaleX), 627 | fromY: Math.floor(downY * scaleY), 628 | toX: Math.floor(upX * scaleX), 629 | toY: Math.floor(upY * scaleY), 630 | duration: (dragTime / 1000).toFixed(2) 631 | }); 632 | downTime = 0; 633 | } 634 | event.stopPropagation(); 635 | event.preventDefault(); 636 | }); 637 | document.addEventListener('mouseup', function(event){ 638 | if(downTime !== 0){ 639 | var upX = event.clientX < screenshot.offsetLeft ? 0 : screenshot.width -1; 640 | var upY = event.clientY; 641 | var dragTime = new Date().getTime() - downTime; 642 | saveCommand('drag', { 643 | fromX: Math.floor(downX * scaleX), 644 | fromY: Math.floor(downY * scaleY), 645 | toX: Math.floor(upX * scaleX), 646 | toY: Math.floor(upY * scaleY), 647 | duration: (dragTime / 1000).toFixed(2) 648 | }); 649 | downTime = 0; 650 | } 651 | }); 652 | screenshot.addEventListener('mousemove', function(event){ 653 | var bestNodeInfo = { 654 | node: null, 655 | boundSize: 0 656 | }; 657 | var x = Math.floor(event.offsetX * scaleX); 658 | var y = Math.floor(event.offsetY * scaleY); 659 | getBestNode(appTree, x, y, bestNodeInfo); 660 | var node = bestNodeInfo.node; 661 | if(node){ 662 | var offsetLeft = screenshot.offsetLeft; 663 | var offsetTop = screenshot.offsetTop; 664 | 665 | var left = node.startX / scaleX; 666 | var top = node.startY / scaleY; 667 | var width = node.endX / scaleX - left; 668 | var height = node.endY / scaleY - top; 669 | 670 | showLine(left + offsetLeft, top + offsetTop, width, height); 671 | } 672 | else{ 673 | hideLine(); 674 | } 675 | }); 676 | document.addEventListener('keypress', function(event){ 677 | if(!isLoading && !isShowDialog){ 678 | showTextDailog(event.key); 679 | event.stopPropagation(); 680 | event.preventDefault(); 681 | } 682 | }); 683 | 684 | document.addEventListener('paste', function(event){ 685 | if(!isLoading && !isShowDialog){ 686 | var text = event.clipboardData.getData('text'); 687 | if(text){ 688 | saveCommand('sendKeys', text+(mobilePlatform === 'iOS' ? '\n' : '{ESCAPE}')); 689 | } 690 | } 691 | }); 692 | 693 | // 计算字节长度,中文两个字节 694 | function byteLen(text){ 695 | var count = 0; 696 | for(var i=0,len=text.length;i 255 ? 2 : 1; 699 | } 700 | return count; 701 | } 702 | 703 | // 从左边读取限制长度的字符串 704 | function leftstr(text, limit){ 705 | var substr = ''; 706 | var count = 0; 707 | var char; 708 | for(var i=0,len=text.length;i 255 ? 2 : 1; 712 | if(count >= limit){ 713 | return substr; 714 | } 715 | } 716 | return substr; 717 | } 718 | 719 | })(); 720 | 721 | -------------------------------------------------------------------------------- /chrome-extension/js/start.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var frmStart = document.getElementById('formStart'); 3 | var txtUrl = document.getElementById('url'); 4 | var btnStart = document.getElementById('btnStart'); 5 | var lstCommons = document.getElementById('commons'); 6 | 7 | // i18n 8 | var i18n = {}; 9 | var __ = function(str){ 10 | var args = arguments; 11 | str = i18n[str] || str; 12 | var count = 0; 13 | str = str.replace(/%s/g, function(){ 14 | count ++; 15 | return args[count] || ''; 16 | }); 17 | return str; 18 | }; 19 | 20 | var testVars = {}; 21 | 22 | // 全局事件 23 | var mapGlobalEvents = {}; 24 | var eventPort = chrome.extension.connect(); 25 | var GlobalEvents = { 26 | on: function(type, handler){ 27 | var arrEvents = mapGlobalEvents[type] || []; 28 | arrEvents.push(handler); 29 | mapGlobalEvents[type] = arrEvents; 30 | }, 31 | emit: function(type, data){ 32 | eventPort.postMessage({ 33 | type: type, 34 | data: data 35 | }); 36 | }, 37 | _emit: function(type, data){ 38 | var arrEvents = mapGlobalEvents[type] || []; 39 | arrEvents.forEach(function(handler){ 40 | handler(data); 41 | }); 42 | } 43 | }; 44 | eventPort.onMessage.addListener(function(msg) { 45 | GlobalEvents._emit(msg.type, msg.data); 46 | }); 47 | 48 | var mapParams = {}; 49 | location.search.replace(/([^\?=]+)=([^&]*)/ ,function(all, key, value){ 50 | mapParams[key] = value; 51 | }); 52 | 53 | // load config 54 | function updateConfig(config){ 55 | i18n = config.i18n; 56 | testVars = config.testVars; 57 | txtUrl.setAttribute('placeholder', __('jump_placeholder')); 58 | btnStart.textContent = __('start_button'); 59 | var specLists = config.specLists; 60 | var arrHtmls = []; 61 | for(var i in specLists){ 62 | arrHtmls.push(''); 63 | } 64 | lstCommons.innerHTML = arrHtmls.join(''); 65 | } 66 | GlobalEvents.on('updateConfig', updateConfig); 67 | chrome.runtime.sendMessage({ 68 | type: 'initBackService', 69 | data: { 70 | port: mapParams.port 71 | } 72 | }); 73 | 74 | txtUrl.focus(); 75 | frmStart.onsubmit = function(){ 76 | var url = txtUrl.value; 77 | url = url.replace(/^\s+|\s+$/g, ''); 78 | if(/^([\w-]+\.)+(com|net|org|com\.cn)(\s+|$)/.test(url)){ 79 | url = 'http://' + url; 80 | } 81 | var varStr = url; 82 | try{ 83 | varStr = eval('\`'+varStr+'\`'); 84 | } 85 | catch(e){ 86 | alert(e); 87 | return false; 88 | } 89 | varStr = getVarStr(varStr); 90 | if(/^https?:\/\//i.test(url) || /^https?:\/\//i.test(varStr)){ 91 | chrome.runtime.sendMessage({ 92 | type: 'command', 93 | data: { 94 | frame: null, 95 | cmd: 'url', 96 | data: url 97 | } 98 | }); 99 | location.href = varStr; 100 | } 101 | else if(/\.js$/.test(url)){ 102 | chrome.runtime.sendMessage({ 103 | type: 'command', 104 | data: { 105 | frame: null, 106 | cmd: 'module', 107 | data: url 108 | } 109 | }); 110 | } 111 | else{ 112 | alert(__('jump_alert')); 113 | } 114 | return false; 115 | } 116 | 117 | function getVarStr(str){ 118 | if(typeof str === 'string'){ 119 | return str.replace(/\{\{(.+?)\}\}/g, function(all, key){ 120 | return testVars[key] || ''; 121 | }); 122 | } 123 | else{ 124 | return str; 125 | } 126 | } 127 | })(); 128 | 129 | -------------------------------------------------------------------------------- /chrome-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "UI Recorder", 4 | "description": "UI Recorder for chrome", 5 | "version": "1.0.0", 6 | "browser_action": { 7 | "default_icon": "img/icon.png", 8 | "default_title": "UI Recorder" 9 | }, 10 | "content_scripts": [{ 11 | "matches": [ "http://*/*", "https://*/*" ], 12 | "all_frames": true, 13 | "match_about_blank": true, 14 | "run_at": "document_start", 15 | "js": [ 16 | "js/foreground.js", 17 | "js/jquery-3.4.1.min.js" 18 | ] 19 | }], 20 | "web_accessible_resources": [ 21 | "js/foreground.js", 22 | "img/hover.png", 23 | "img/sleep.png", 24 | "img/expect.png", 25 | "img/jump.png", 26 | "img/end.png", 27 | "img/ok.png", 28 | "img/cancel.png", 29 | "img/vars.png", 30 | "img/success.png", 31 | "img/fail.png", 32 | "img/text.png", 33 | "img/alert.png", 34 | "img/back.png", 35 | "img/jscode.png" 36 | ], 37 | "background": { 38 | "scripts": [ "js/background.js" ] 39 | }, 40 | "icons": { 41 | "16": "img/icon.png", 42 | "64": "img/icon-64.png", 43 | "128": "img/icon-128.png" 44 | }, 45 | "permissions": ["tabs", "webNavigation", "\u003Call_urls\u003E", "notifications"], 46 | "content_security_policy": "script-src 'self' 'unsafe-eval';" 47 | } 48 | -------------------------------------------------------------------------------- /chrome-extension/mobile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UI Recorder 6 | 7 | 157 | 158 | 159 |
160 | 161 |
162 |
163 | 164 |
165 |
166 |
167 |

168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /chrome-extension/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UI Recorder 7 | 8 | 247 | 248 | 249 | 250 | 251 | 252 |
253 |
254 | 255 | 256 | 257 |
258 |
© alibaba.com
259 |
260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /doc/zh-cn/pc-advanced.md: -------------------------------------------------------------------------------- 1 | UIRecorder PC高级使用 2 | ============================ 3 | 4 | 如何解决属性值不稳定问题? 5 | ------------------- 6 | 7 | 有很多开发会写一些随机的属性值,例如某些随机的id值,这种属性值会导致录制的脚本完全无法持续运行。 8 | 9 | 针对这个问题,我们提供了3套解决方案 10 | 11 | 1. `属性值黑名单正则`: 可以编写正则表达式过滤掉那些不稳定的属性值,例如:`/attr_value_\d+/`,如果需要过滤多种属性值,可以这么写:`/attr_value1_\d+|attr_value2_\d+/`。这个值可以在`uirecorder init`命令中进行配置,也可以在录制过程中的录制面板上即时修改。 12 | 2. `属性开关`:在录制面板上,可以通过临时切换不同属性项的开启和关闭,灵活组合出适合自己业务的PATH生成方案,例如某些场景下不适合用`text`,就可以临时将`text`属性项关闭掉。 13 | 3. `class值黑名单正则`:对于某些不稳定的class值,我们同样提供了黑名单功能,此功能需要通过`uirecorder init`命令进行配置。 14 | 15 | 变量功能怎么用? 16 | ------------------- 17 | 18 | 编辑config.json 19 | 20 | { 21 | "recorder": { 22 | ... 23 | }, 24 | "webdriver": { 25 | ... 26 | }, 27 | "vars": { 28 | "productId": "123456", 29 | "productName": "mp3" 30 | } 31 | } 32 | 33 | 1. 开始页面输入: `http://xxx.com/product?id={{productId}}` 34 | 2. 录制界面中使用工具面创建新变量 35 | 3. 录制界面中使用工具面更新旧变量的值 36 | 4. 录制界面中使用工具面板跳转URL: `http://xxx.com/product?id={{productId}}` 37 | 5. 录制界面中使用工具面板插入变量字符串: `{{productName}}` 或 `aaa{{productName}}bbb` 38 | 6. 断言中使用变量字符串: `{{productName}}` 或 `aaa{{productName}}bbb` 39 | 40 | 提示: 所有变量字符串均支持JS语法的模板字符串,例如:`{{productName}}, ${new Date().getTime()}, ${parseInt(testVars.a)+parseInt(testVars.b)}` 41 | 42 | 怎么录制及使用公共脚本? 43 | ------------------- 44 | 45 | 1. 录制 `commons/login.mod.js` 46 | 2. 录制 `sample/test.spec.js` 47 | 48 | 1. 在录制浏览器的开始页面时输入 `login.mod.js`,或者在输入框的右端点击下拉小三角,选择需要的脚本 49 | 2. 或者在录制中间页面点击:`脚本跳转`,随后同上 50 | 3. 当`login.mod.js`加载完成后,继续别的步骤的录制 51 | 52 | 3. `source run.sh sample/test.spec.js` ( Linux|Mac ) 或 `run.bat sample/test.spec.js` ( Windows ) 53 | 54 | 如何录制文件上传步骤? 55 | ------------------- 56 | 57 | 1. UI Recorder仅支持Native文件上传, 不支持FLASH上传 58 | 2. 直接点击`` 或点击 ``, 占位按钮必需要用`role`或`data-role`标注为`upload` 59 | 3. 上传的文件必需保存在`uploadfiles/`文件夹中 60 | 61 | 如何断点调试生成的脚本? 62 | ------------------- 63 | 64 | 1. 安装 [Visual Studio Code](https://code.visualstudio.com/) ,然后打开它 65 | 2. 在vs code中打开项目根目录 66 | 3. 打开测试脚本, 添加断点 67 | 4. 按 `F5` 键执行脚本, 按 `F10` 键执行下一行 68 | 69 | 如何断言浏览器eval js代码后的结果? 70 | ---------------- 71 | 72 | 1. `添加断言`, 选择类型: `jscode` 73 | 2. 同步模式: `return document.title` 74 | 3. 函数模式: 75 | 76 | function(){ 77 | var str = "aaa"; 78 | return str; 79 | } 80 | 81 | 4. 异步模式: 82 | 83 | function(done){ 84 | setTimeout(function(){ 85 | done(123); 86 | }, 100); 87 | } 88 | 89 | 如何在断言前隐藏DOM结点? 90 | ---------------- 91 | 92 | 1. `uirecorder init` 93 | 2. 在初始化`断言前隐藏`选项时,输入需要隐藏的css选择器 94 | 3. `uirecorder start` 95 | 4. UIRecorder会在断言前隐藏所有匹配的DOM结点,然后就可以断言那些隐藏在mask层后面的DOM 96 | 97 | 如何录制可选的点击? 98 | ---------------- 99 | 100 | 某些步骤不是非常重要,但却偶尔会出现,这些步骤会总是断言为成功。 101 | 102 | 1. 按下'Alt'键 103 | 2. 点击目标DOM 104 | 105 | 如何使用图片对比功能? 106 | ---------------- 107 | 108 | 1. 安装GraphicsMagick 109 | 110 | > brew install graphicsmagick (Mac) 111 | 112 | > sudo apt-get install graphicsmagick (Linux) 113 | 114 | > http://www.graphicsmagick.org/download.html (Windows) 115 | 116 | 2. 添加图片对比断言 117 | 118 | > 选择断言类型: imgdiff 119 | 120 | > 选择目标控件 121 | 122 | 3. 当业务变化时,我们可以通过以下命令更新基线图片 123 | 124 | > `source run.sh sample/test.spec.js --rebuilddiff` (Mac | Linux) 125 | 126 | > `run.bat sample/test.spec.js --rebuilddiff` (Windows) 127 | 128 | 如何导出原数据? 129 | ---------------- 130 | 131 | 如果希望基于UIRecorder录制出来的步骤生成JAVA等别的语言自动化脚本,可以使用我们的原数据导出功能。 132 | 133 | 此功能可以在生成js语法的自动化脚本同时,也生成json格式的原数据。基于此json文件,我们就可以自由的翻译成任何语言的自动化脚本。 134 | 135 | 1. `uirecorder start --raw` 136 | 2. 录制完后,就可以获得2个文件: `sample/test.spec.js`, `sample/test.spec.json` 137 | 138 | 如何接入Jenkins? 139 | ---------------- 140 | 141 | 1. 添加命令 142 | 143 | source ./install.sh 144 | source ./run.sh 145 | 146 | 2. 添加报告 147 | 148 | > [JUnit](https://wiki.jenkins-ci.org/display/JENKINS/JUnit+Plugin): `reports/index.xml` 149 | 150 | > [HTML](https://wiki.jenkins-ci.org/display/JENKINS/HTML+Publisher+Plugin): `reports/index.html` 151 | 152 | 国内用户可以通过oschina和cnpm提升部署效率,修改install.sh如下: 153 | 154 | ls ~/nvm || git clone https://git.oschina.net/yaniswang/nvm.git ~/nvm 155 | source ~/nvm/nvm.sh 156 | export NVM_NODEJS_ORG_MIRROR="http://npm.taobao.org/mirrors/node" 157 | nvm install v7.10.0 158 | npm install --registry=https://registry.npmmirror.com 159 | 160 | 如何失败时才生成截图? 161 | ------------------- 162 | 163 | 1. 编辑文件:`package.json`, 确保`mochawesome-uirecorder`版本在`1.5.22`及以上 164 | 2. 在`--reporter mochawesome-uirecorder`后面添加:` --reporter-options copyShotOnlyFail=true` 165 | -------------------------------------------------------------------------------- /doc/zh-cn/pc-faq.md: -------------------------------------------------------------------------------- 1 | 常见问题 2 | ================= 3 | 4 | 1. `SyntaxError: missing ) after argument list` 5 | 6 | 解决方案:升级到NodeJs v10 以上版本 7 | 8 | 2. `session not created exception from disconnected: unable to connect to renderer` 9 | 10 | 解决方案:系统hosts文件中添加`127.0.0.1 localhost` 11 | -------------------------------------------------------------------------------- /doc/zh-cn/pc-standard.md: -------------------------------------------------------------------------------- 1 | UIRecorder PC标准入门 2 | =================== 3 | 4 | 如何录制正常网页操作步骤? 5 | ------------------- 6 | 7 | 1. 首先安装UIRecorder,这个请参考项目介绍首页:[https://github.com/alibaba/uirecorder](https://github.com/alibaba/uirecorder) 8 | 2. 创建新目录并进入,然后初始化UIRecorder工程目录:`uirecorder init`, 根据具体情况输入相关的参数,几个回车后,我们的准备工作就结束了,这里详细介绍下几个参数分别是什么作用 9 | 10 | 1. `Path扩展属性配置,除id,name,class之外`: 此参数,用来定义网页DOM中哪些属性可以用来定位我们的控件,默认值已经包括了主流的属性,一般情况下不需要修改 11 | 2. `属性值黑名单正则`:网页中当某些属性值是随机或者不稳定的时候,我们可以通过这个配置来忽略那些属性值,从而保证我们的自动化脚本稳定性,例如我们可以这样配置:`/attr_name_\d+/`,如果需要配置多个属性,可以这么写:`/attr1_\d+|attr2_\d+|/`,详细更多的正则教程请自行搜索 12 | 3. `class值黑名单正则`:某些场景下,class属性的值会干扰自动化录制,比如当鼠标移到某个控件上时会动态的添加一个class样式名称,就可能会被录制到PATH中,从而导致自动化无法稳定重放。此时就可以将那个class名称加到黑名单中,例如:`/class_name/`,详细值的配置请参考属性值黑名单 13 | 4. `断言前隐藏`:某些业务场景下,会在最上层显示一层透明的div,用来实现一些高级的富应用需求,会导致无法直接断言透明div下方的控件。此种场景下,可以将透明div的PATH配置在此属性中,即可实现在断言时自动移除透明的div,断言后再自动恢复 14 | 5. `WebDriver域名或IP`:WebDriver执行机的IP地址,默认是指向本地,如果有部署grid执行机集群,可以指向对应的执行机集群IP 15 | 6. `WebDriver端口号`:WebDriver端口号,一般情况下是4444端口号,不需要修改 16 | 7. `需要同时测试的浏览器列表`: 需要同时测试的浏览器列表,例如:chrome, firefox, ie 11,可以根据本地或远程执行机集群所支持的浏览器类型自行输入 17 | 18 | 3. 输入`uirecorder`,会要求输入相关的几个参数, 我们这里介绍下几个参数的意义 19 | 20 | 1. `测试脚本文件名`: 录制后保存到哪个文件,支持直接输入目录名,例如:test/aaa.spec.js,我们要求脚本文件名必需是xxx.spec.js,否则会导致无法定位到 21 | 2. `打开同步校验浏览器?`:校验浏览器是用来实现边录边跑,同步的校验录制的步骤是否能够稳定的重现,避免录制后进行大量的调试。从而实现一次录制,即可稳定重现,极大的提高成功率和稳定性。默认值已经是打开,按回车即可。 22 | 3. `浏览器大小`:默认值是1024x768,如果对于浏览器窗口大小没有特殊要求,用默认值回车即可。如果某些场景要求特定大小,那么可以自己指定特定的大小,例如:1024x768 23 | 24 | 当以上4个参数确认后,我们将看到一个黑背影的开始界面。此时,我们仅需输入需要测试的目标网址,即可正式开始我们的录制之旅。 25 | 26 | 常规录制说明: 27 | 28 | 1. 对于常规的网页操作行为,我们没有任何学习门槛,按照正常的网页操作步骤进行即可,UIRecorder会在后台默默的记录并生成自动化脚本 29 | 2. 除了鼠标的悬停操作,我们需要通过录制面板,在目标控件上手工添加一个悬停。例如,二级菜单就需要鼠标移到一级菜单后,才能在二级菜单进行点击操作。 30 | 3. 如何录制三级甚至四级的多级菜单? 31 | 32 | 按住`Ctrl/Command键`,然后点击`添加悬停`按钮,即可进入持续悬停模式。进入此模式后,每次添加悬停后,会持续处于事件拦截状态,不用担心鼠标移动会导致二级菜单缩回,然后就可以持续的添加更多的悬停操作。 33 | 34 | 4. 如何在悬停后断言? 35 | 36 | 进入`持续悬停模式`后,同时也可以解决悬停后的断言问题。如果希望结束`持续悬停模式`,请再次点击`结束悬停`按钮,即可退出此模式。 37 | 38 | 如何添加断言? 39 | ------------------- 40 | 41 | 当进行一系列操作后,往往我们需要添加一系列的断言,用来判断操作后的结果是不是正确的。 42 | 43 | 在操作录制后,我们仅需点击录制面板上的`添加断言`按钮即可,点击按钮后,我们可以发现有以下几个选项: 44 | 45 | 1. `延迟时间`:用来设置本次断言延迟一定的时间后才执行,一般用来解决某些异步操作时间不确定的问题。 46 | 2. `断言类型`:我们提供子丰富的断言类型,不同的控件类型要选择合适的断言类型,这里针对每个断言类型进行介绍 47 | 48 | 1. `val`: 断言输入框的值 49 | 2. `text`: 断言文本的内容 50 | 3. `displayed`: 断言控件是否处于显示状态 51 | 4. `enabled`: 断言当前控件是否可用(没有禁用) 52 | 5. `selected`: 断言当前控件是否打勾选中了 53 | 6. `attr`: 断言当前DOM的属性值 54 | 7. `css`: 断言当前DOM的CSS值 55 | 8. `url`: 断言当前网页的URL地址 56 | 9. `title`: 断言当前网页的title标题 57 | 10. `cookie`: 断言当前网页的cookie值 58 | 11. `localStorage`: 断言当前网页的localStorage 59 | 12. `sessionStorage`: 断言当前网页的sessionStorage 60 | 13. `alert`: 断言弹出的alert窗口的提示文本 61 | 14. `jscode`: 在浏览器端执行自定义的JS代码,断言JS代码的返回值 62 | 15. `count`: 断言控件匹配的数量 63 | 16. `imgdiff`: 断言当前控件的图片差异,可以自定义图片差异的百分比 64 | 65 | 3. `断言DOM`:当前DOM控件的PATH,录制时自动生成,也可以自己进行修改,以在某些特殊场景下进行微调 66 | 4. `比较方式`: 针对读取到的值,如何进行断言,我们解释下每个比较方式 67 | 68 | 1. `equal`: 相等 69 | 2. `notEqual`: 不相等 70 | 3. `contain`: 包含,目标值包含另外一个值 71 | 4. `notContain`: 不包含 72 | 5. `above`: 大于,用于断言数值大于某个值 73 | 6. `below`: 小于,用于断言数值小于某个值 74 | 7. `match`: 匹配正则,一般用于高级断言,例如:/aaa\d+bbb/ 75 | 8. `notMatch`: 不匹配正则 76 | 77 | 5. `断言结果`: 判断的目标值 78 | 79 | 其它录制功能介绍 80 | -------------------- 81 | 82 | 1. `属性开关`:对于某些特殊场景,可以通过灵活的开启或关闭属性项,不同步骤选择不同的PATH生成策略,例如:某些控件不太适合用文本定位,就可以临时先关闭`text`属性项 83 | 2. `属性值黑名单`: 可以即时配置属性值黑名单,立即生效,用来解决随机属性值的问题,修改后记得按一下回车键,让变更立即生效,格式如下:`/aaa_bbb_\d+/` 84 | 3. `执行JS`: 可以用来在浏览器端执行一些扩展功能,例如:`document.title=123;` 85 | 4. `添加延迟`:某些操作后,会发起异步请求,异步请求的完成时间不太确定时,可以通过添加延迟时间提升稳定性,时间单位是毫秒 86 | 5. `脚本跳转`:用来跳到新的URL,或者跳到另外一个脚本,一般情况下用来引用公共脚本,例如登录操作 87 | 88 | 如何部署WebDriver服务? 89 | ---------------- 90 | 91 | 1. 本地部署Selenium standalone server: 92 | 93 | 新开控制台窗口然后执行 94 | 95 | > `npm run server` 96 | 97 | 2. Selenium Grid: [https://github.com/SeleniumHQ/selenium/wiki/Grid2](https://github.com/SeleniumHQ/selenium/wiki/Grid2) 98 | 3. F2etest: [https://github.com/alibaba/f2etest](https://github.com/alibaba/f2etest) 99 | 100 | 录制后如何运行脚本? 101 | -------------------- 102 | 103 | 1. 运行所有脚本: `source run.sh` ( Linux|Mac ) 或 `run.bat` ( Windows ) 104 | 2. 运行单个脚本: `source run.sh sample/test.spec.js` ( Linux|Mac ) 或 `run.bat sample/test.spec.js` ( Windows ) 105 | 106 | 如何临时基于本地浏览器调试脚本? 107 | ---------------- 108 | 109 | 对于常态基于远程执行机跑的场景下,出现问题时,往往需要在本机进行调试,我们可以通过设置环境变量,临时基于本机的浏览器来调试。 110 | 111 | 1. `export webdriver=127.0.0.1:4444` 或 `set webdriver=127.0.0.1:4444` (Windows) 112 | 113 | 提示:端口号是非必填项,例如:`export webdriver=127.0.0.1` 114 | -------------------------------------------------------------------------------- /doc/zh-cn/readme.md: -------------------------------------------------------------------------------- 1 | UIRecorder是一款基于WebDriver、Chrome浏览器、NodeJs等方案共同打造的零成本自动化解决方案。 2 | 3 | 基于几乎零成本的录制方案,我们让任何一个完全没有自动化经验的人,可以1分钟录制出可读性高,且强大的自动化脚本。 4 | 5 | 让所有开发和测试能够最低成本的获得自动化测试的能力,把重复又枯燥的测试工作全部交给计算机,彻底的提高测试效率,解放我们的生产力。 6 | 7 | UIRecorder PC使用手册 8 | ================= 9 | 10 | 1. [标准入门](pc-standard.md) 11 | 2. [高级使用](pc-advanced.md) 12 | 3. [常见问题](pc-faq.md) 13 | 14 | 15 | UIRecorder 无线使用手册 16 | ================= 17 | 18 | 1. [简单配置](https://macacajs.github.io/zh/guide/recorder.html) 19 | -------------------------------------------------------------------------------- /i18n/en.js: -------------------------------------------------------------------------------- 1 | { 2 | "update_tip": " Update available %s → %s\n Run %s to update", 3 | "please_reinit": "Please reinitialize: uirecorder init", 4 | "webdriver_host": "Webdriver host or ip", 5 | "webdriver_port": "Webdriver port", 6 | "webdriver_browsers": "Browsers list", 7 | "file_saved": "file saved", 8 | "file_created": "file created", 9 | "dir_created": "directory created", 10 | "json_parse_failed": "json parse failed!", 11 | "file_missed": "file search failed, please init first!", 12 | "input_spec_name": "Test spec file name:", 13 | "continue_to_record": "File existed, load and continue to record?", 14 | "mobile_app_path": "App Path (extensions: apk, app, zip):", 15 | "mobile_platform": "App platform:", 16 | "open_checker_browser": "Open checker browser?", 17 | "browser_size": "Browser size (example: 1024 x 768):", 18 | "dom_path_config": "Dom path config, extend: id, name, class", 19 | "attr_black_list": "Black list RegExp for attribute value", 20 | "class_black_list": "Black list RegExp for class value", 21 | "hide_before_expect": "Hide before expect", 22 | "exec_succeed": "execute succeed", 23 | "exec_failed": "execute failed", 24 | "recorder_browser_opened": "Recorder browser opened", 25 | "recorder_browser_title": "Recorder browser", 26 | "checker_browser_opened": "Checker browser opened", 27 | "checker_browser_title": "Checker browser", 28 | "recorder_server_closed": "Recorder server closed", 29 | "recorder_browser_closed": "Recorder browser closed", 30 | "checker_browser_closed": "Checker browser closed", 31 | "test_spec_saved": "Recorded test saved: ", 32 | "raw_cmds_saved": "Raw cmds saved: ", 33 | "config_saved": "Config saved: ", 34 | "check_sumary": "Record %s steps, %s steps check succeed, %s steps check failed", 35 | "no_step_recorded": "No step recorded!", 36 | "nocheck_sumary": "Record %s steps %s", 37 | "nocheck": "no checked", 38 | "recorder_server_listen_on": "Recorder server listen on: %s", 39 | "chrome_init_failed": "Chrome init failed!", 40 | "localhost_hosts_tip": "Please try to add `127.0.0.1 localhost` to your hosts!", 41 | "mobile_open_first": "Please connect your mobile, or open emulator first.", 42 | "mobile_open_failed": "Mobile open failed, please check macaca config.", 43 | "exec_succeed": " execute succeed", 44 | "exec_failed": " execute failed", 45 | "chrome": { 46 | "jump_placeholder": "Please input url or test file name", 47 | "jump_alert": "Please input correct url or test file name!", 48 | "start_button": "Start Record", 49 | "icon_record_tip": "Recording...click for end record", 50 | "icon_end_tip": "Record ended", 51 | "icon_end_msg": "Record is ended, please reopen client!", 52 | "module_start_title": "Test is loading", 53 | "module_start_message": "Test loading: %s, please dont't press any keyboard or mouse when loading!", 54 | "module_end_title": "Test loaded", 55 | "module_end_message": "Test load %s: %s, please continue recording.", 56 | "exec_succeed": "execute succeed", 57 | "exec_failed": "execute failed", 58 | "succeed": "succeed", 59 | "failed": "failed", 60 | "loading": "Loading……", 61 | "attr_switch": "Attr switch: ", 62 | "attr_black": "Attr black: ", 63 | "attr_black_tip": "Please input RegExp for filter attr value, E.g.: /black_val/", 64 | "button_hover_on_text": "Add Hover", 65 | "button_hover_off_text": "End Hover", 66 | "button_sleep_text": "Add Sleep", 67 | "button_expect_text": "Add Expect", 68 | "button_vars_text": "Use Var", 69 | "button_jscode_text": "Run JS", 70 | "button_jump_text": "Jump to", 71 | "button_end_text": "End Record", 72 | "button_text_text": "Input Text", 73 | "button_alert_text": "Alert Cmd", 74 | "button_back_text": "Back Button", 75 | "dialog_ok": "Ok", 76 | "dialog_cancel": "Cancel", 77 | "dialog_expect_title": "Add Expect:", 78 | "dialog_expect_sleep": "Delay Time: ", 79 | "dialog_expect_type": "Expect Type: ", 80 | "dialog_expect_dom": "Expect DOM: ", 81 | "dialog_expect_path": "Expect Path: ", 82 | "dialog_expect_param": "Expect param: ", 83 | "dialog_expect_compare": "Expect compare: ", 84 | "dialog_expect_to": "Expect to: ", 85 | "dialog_vars_title": "Use Var: ", 86 | "dialog_vars_type": "Use type: ", 87 | "dialog_vars_type_insert": "Insert var", 88 | "dialog_vars_type_update": "Update var", 89 | "dialog_vars_name": "Var name: ", 90 | "dialog_vars_addname": "Add var", 91 | "dialog_vars_name_empty": "Name empty!", 92 | "dialog_vars_name_duplicated": "Name duplicated!", 93 | "dialog_vars_value": "Var value: ", 94 | "dialog_vars_template": "Var template: ", 95 | "dialog_vars_template_result": "Template result: ", 96 | "dialog_vars_update_type": "Update type: ", 97 | "dialog_vars_update_dom": "Update DOM: ", 98 | "dialog_vars_update_param": "Update param: ", 99 | "dialog_vars_update_regex": "Update regex: ", 100 | "dialog_jump_title": "Jump to: ", 101 | "dialog_jump_target": "Jump target: ", 102 | "dialog_sleep_title": "Add sleep: ", 103 | "dialog_sleep_time": "Sleep time: ", 104 | "dialog_sleep_time_tip": "Please input sleep time", 105 | "dialog_text_title": "Input Text: ", 106 | "dialog_text_content": "Text Content: ", 107 | "dialog_text_content_tip": "Please input text content", 108 | "dialog_alert_title": "Alert Cmd: ", 109 | "dialog_alert_option": "Alert option: ", 110 | "dialog_alert_option_accpet": "Accpet", 111 | "dialog_alert_option_dismiss": "Dismiss", 112 | "dialog_jscode_title": "Run JS", 113 | "dialog_jscode_code": "JS Code", 114 | "dialog_jscode_tip": "Please input jscode run in browser side,E.g.: document.title='test';" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /i18n/zh-cn.js: -------------------------------------------------------------------------------- 1 | { 2 | "update_tip": " 发现新版本: %s → %s\n 执行命令升级:%s", 3 | "please_reinit": "请重新初始化: uirecorder init", 4 | "webdriver_host": "WebDriver域名或IP", 5 | "webdriver_port": "WebDriver端口号", 6 | "webdriver_browsers": "需要同时测试的浏览器列表", 7 | "file_saved": "文件保存成功", 8 | "file_created": "文件创建成功", 9 | "dir_created": "文件夹创建成功", 10 | "json_parse_failed": "JSON解析失败!", 11 | "file_missed": "文件查找失败,请先初始化!", 12 | "input_spec_name": "测试脚本文件名:", 13 | "continue_to_record": "文件已存在,加载并继续录制吗?", 14 | "mobile_app_path": "App路径 (扩展名: apk, app, zip):", 15 | "mobile_platform": "App平台:", 16 | "open_checker_browser": "打开同步校验浏览器?", 17 | "browser_size": "浏览器大小 (格式:1024 x 768):", 18 | "dom_path_config": "Path扩展属性配置,除id,name,class之外", 19 | "attr_black_list": "属性值黑名单正则", 20 | "class_black_list": "class值黑名单正则", 21 | "hide_before_expect": "断言前隐藏", 22 | "exec_succeed": "执行成功", 23 | "exec_failed": "执行失败", 24 | "recorder_browser_opened": "录制浏览器已开启", 25 | "recorder_browser_title": "录制浏览器", 26 | "checker_browser_opened": "校验浏览器已开启", 27 | "checker_browser_title": "校验浏览器", 28 | "recorder_server_closed": "录制服务器已关闭", 29 | "recorder_browser_closed": "录制浏览器已关闭", 30 | "checker_browser_closed": "校验浏览器已关闭", 31 | "test_spec_saved": "录制脚本已保存: ", 32 | "raw_cmds_saved": "原数据已保存: ", 33 | "config_saved": "配置已保存: ", 34 | "check_sumary": "共录制%s个步骤,其中校验通过: %s个,校验失败: %s个", 35 | "no_step_recorded": "无步骤录制!", 36 | "nocheck_sumary": "共录制%s个步骤 %s", 37 | "nocheck": "未经过校验", 38 | "recorder_server_listen_on": "录制服务器监听在端口: %s", 39 | "chrome_init_failed": "Chrome打开失败!", 40 | "localhost_hosts_tip": "请尝试添加 `127.0.0.1 localhost` 到您的hosts文件中!", 41 | "mobile_open_first": "请先连接手机或打开模拟器。", 42 | "mobile_open_failed": "手机打开失败,请检查macaca是否配置正确?", 43 | "exec_succeed": " 执行成功", 44 | "exec_failed": " 执行失败", 45 | "chrome": { 46 | "jump_placeholder": "请输入需要测试的URL 或 脚本文件名!", 47 | "jump_alert": "请输入正确的URL 或 脚本文件名!", 48 | "start_button": "开始录制", 49 | "icon_record_tip": "录制中……点击结束录制", 50 | "icon_end_tip": "录制已结束", 51 | "icon_end_msg": "录制已经结束,请从客户端重新初始化!", 52 | "module_start_title": "脚本加载开始", 53 | "module_start_message": "开始加载脚本: %s,加载过程中请勿进行任何键盘和鼠标操作。", 54 | "module_end_title": "脚本加载结束", 55 | "module_end_message": "脚本加载%s: %s,请继续录制操作。", 56 | "exec_succeed": "执行成功", 57 | "exec_failed": "执行失败", 58 | "succeed": "成功", 59 | "failed": "失败", 60 | "loading": "等待加载……", 61 | "attr_switch": "属性开关: ", 62 | "attr_black": "属性黑名单: ", 63 | "attr_black_tip": "请输入过滤属性值的正则表达式, 例如:/black_val/", 64 | "button_hover_on_text": "添加悬停", 65 | "button_hover_off_text": "结束悬停", 66 | "button_sleep_text": "添加延迟", 67 | "button_expect_text": "添加断言", 68 | "button_vars_text": "使用变量", 69 | "button_jscode_text": "执行JS", 70 | "button_jump_text": "脚本跳转", 71 | "button_end_text": "结束录制", 72 | "button_text_text": "输入文字", 73 | "button_alert_text": "Alert命令", 74 | "button_back_text": "后退按键", 75 | "dialog_ok": "确认", 76 | "dialog_cancel": "取消", 77 | "dialog_expect_title": "添加断言: ", 78 | "dialog_expect_sleep": "延迟时间: ", 79 | "dialog_expect_type": "断言类型: ", 80 | "dialog_expect_dom": "断言DOM: ", 81 | "dialog_expect_path": "断言Path: ", 82 | "dialog_expect_param": "断言参数: ", 83 | "dialog_expect_compare": "比较方式: ", 84 | "dialog_expect_to": "断言结果: ", 85 | "dialog_vars_title": "使用变量: ", 86 | "dialog_vars_type": "使用方式: ", 87 | "dialog_vars_type_insert": "插入变量", 88 | "dialog_vars_type_update": "更新变量", 89 | "dialog_vars_name": "变量名: ", 90 | "dialog_vars_addname": "添加变量", 91 | "dialog_vars_name_empty": "变量名不能为空!", 92 | "dialog_vars_name_duplicated": "变量名重复!", 93 | "dialog_vars_value": "变量值: ", 94 | "dialog_vars_template": "变量模板: ", 95 | "dialog_vars_template_result": "模板结果: ", 96 | "dialog_vars_update_type": "取值方式: ", 97 | "dialog_vars_update_dom": "取值DOM: ", 98 | "dialog_vars_update_param": "取值参数: ", 99 | "dialog_vars_update_regex": "取值正则: ", 100 | "dialog_jump_title": "脚本跳转: ", 101 | "dialog_jump_target": "跳转目标: ", 102 | "dialog_sleep_title": "添加延迟: ", 103 | "dialog_sleep_time": "延迟时间: ", 104 | "dialog_sleep_time_tip": "请输入延迟时间,单位毫秒", 105 | "dialog_text_title": "输入文字: ", 106 | "dialog_text_content": "文字内容: ", 107 | "dialog_text_content_tip": "请输入文字", 108 | "dialog_alert_title": "Alert命令: ", 109 | "dialog_alert_option": "Alert选项: ", 110 | "dialog_alert_option_accpet": "接受", 111 | "dialog_alert_option_dismiss": "拒绝", 112 | "dialog_jscode_title": "执行JS", 113 | "dialog_jscode_code": "JS代码", 114 | "dialog_jscode_tip": "请输入浏览器端执行的JS代码,例如: document.title='test';" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /i18n/zh-tw.js: -------------------------------------------------------------------------------- 1 | { 2 | "update_tip": " 發現新版本: %s → %s\n 執行命令升級:%s", 3 | "please_reinit": "請重新初始化: uirecorder init", 4 | "webdriver_host": "WebDriver域名或IP", 5 | "webdriver_port": "WebDriver端口號", 6 | "webdriver_browsers": "需要同時測試的瀏覽器列表", 7 | "file_saved": "文件保存成功", 8 | "file_created": "文件創建成功", 9 | "dir_created": "文件夾創建成功", 10 | "json_parse_failed": "JSON解析失敗!", 11 | "file_missed": "文件查找失敗,請先初始化!", 12 | "input_spec_name": "測試腳本文件名:", 13 | "continue_to_record": "文件已存在,加載並繼續錄制嗎?", 14 | "mobile_app_path": "App路徑 (擴展名: apk, app, zip):", 15 | "mobile_platform": "App平台:", 16 | "open_checker_browser": "打開同步校驗瀏覽器?", 17 | "browser_size": "瀏覽器大小 (格式:1024 x 768):", 18 | "dom_path_config": "Path擴展屬性配置,除id,name,class之外", 19 | "attr_black_list": "屬性值黑名單正則", 20 | "class_black_list": "class值黑名單正則", 21 | "hide_before_expect": "斷言前隱藏", 22 | "exec_succeed": "執行成功", 23 | "exec_failed": "執行失敗", 24 | "recorder_browser_opened": "錄制瀏覽器已開啓", 25 | "recorder_browser_title": "錄制瀏覽器", 26 | "checker_browser_opened": "校驗瀏覽器已開啓", 27 | "checker_browser_title": "校驗瀏覽器", 28 | "recorder_server_closed": "錄制服務器已關閉", 29 | "recorder_browser_closed": "錄制瀏覽器已關閉", 30 | "checker_browser_closed": "校驗瀏覽器已關閉", 31 | "test_spec_saved": "錄制腳本已保存: ", 32 | "raw_cmds_saved": "原數據已保存: ", 33 | "config_saved": "配置已保存: ", 34 | "check_sumary": "共錄制%s個步驟,其中校驗通過: %s個,校驗失敗: %s個", 35 | "no_step_recorded": "無步驟錄制!", 36 | "nocheck_sumary": "共錄制%s個步驟 %s", 37 | "nocheck": "未經過校驗", 38 | "recorder_server_listen_on": "錄制服務器監聽在端口: %s", 39 | "chrome_init_failed": "Chrome打開失敗!", 40 | "localhost_hosts_tip": "請嘗試添加 `127.0.0.1 localhost` 到您的hosts文件中!", 41 | "mobile_open_first": "請先連接手機或打開模擬器。", 42 | "mobile_open_failed": "手機打開失敗,請檢查macaca是否配置正確?", 43 | "exec_succeed": " 執行成功", 44 | "exec_failed": " 執行失敗", 45 | "chrome": { 46 | "jump_placeholder": "請輸入需要測試的URL 或 腳本文件名!", 47 | "jump_alert": "請輸入正確的URL 或 腳本文件名!", 48 | "start_button": "開始錄制", 49 | "icon_record_tip": "錄制中……點擊結束錄制", 50 | "icon_end_tip": "錄制已結束", 51 | "icon_end_msg": "錄制已經結束,請從客戶端重新初始化!", 52 | "module_start_title": "腳本加載開始", 53 | "module_start_message": "開始加載腳本: %s,加載過程中請勿進行任何鍵盤和鼠標操作。", 54 | "module_end_title": "腳本加載結束", 55 | "module_end_message": "腳本加載%s: %s,請繼續錄制操作。", 56 | "exec_succeed": "執行成功", 57 | "exec_failed": "執行失敗", 58 | "succeed": "成功", 59 | "failed": "失敗", 60 | "loading": "等待加載……", 61 | "attr_switch": "屬性開關: ", 62 | "attr_black": "屬性黑名單: ", 63 | "attr_black_tip": "請輸入過濾屬性值的正則表達式, 例如:/black_val/", 64 | "button_hover_on_text": "添加懸停", 65 | "button_hover_off_text": "結束懸停", 66 | "button_sleep_text": "添加延遲", 67 | "button_expect_text": "添加斷言", 68 | "button_vars_text": "使用變量", 69 | "button_jscode_text": "執行JS", 70 | "button_jump_text": "腳本跳轉", 71 | "button_end_text": "結束錄制", 72 | "button_text_text": "輸入文字", 73 | "button_alert_text": "Alert命令", 74 | "button_back_text": "後退按鍵", 75 | "dialog_ok": "確認", 76 | "dialog_cancel": "取消", 77 | "dialog_expect_title": "添加斷言: ", 78 | "dialog_expect_sleep": "延遲時間: ", 79 | "dialog_expect_type": "斷言類型: ", 80 | "dialog_expect_dom": "斷言DOM: ", 81 | "dialog_expect_path": "斷言Path: ", 82 | "dialog_expect_param": "斷言參數: ", 83 | "dialog_expect_compare": "比較方式: ", 84 | "dialog_expect_to": "斷言結果: ", 85 | "dialog_vars_title": "使用變量: ", 86 | "dialog_vars_type": "使用方式: ", 87 | "dialog_vars_type_insert": "插入變量", 88 | "dialog_vars_type_update": "更新變量", 89 | "dialog_vars_name": "變量名: ", 90 | "dialog_vars_addname": "添加變量", 91 | "dialog_vars_name_empty": "變量名不能為空!", 92 | "dialog_vars_name_duplicated": "變量名重復!", 93 | "dialog_vars_value": "變量值: ", 94 | "dialog_vars_template": "變量模板: ", 95 | "dialog_vars_template_result": "模板結果: ", 96 | "dialog_vars_update_type": "取值方式: ", 97 | "dialog_vars_update_dom": "取值DOM: ", 98 | "dialog_vars_update_param": "取值參數: ", 99 | "dialog_vars_update_regex": "取值正則: ", 100 | "dialog_jump_title": "腳本跳轉: ", 101 | "dialog_jump_target": "跳轉目標: ", 102 | "dialog_sleep_title": "添加延遲: ", 103 | "dialog_sleep_time": "延遲時間: ", 104 | "dialog_sleep_time_tip": "請輸入延遲時間,單位毫秒", 105 | "dialog_text_title": "輸入文字: ", 106 | "dialog_text_content": "文字內容: ", 107 | "dialog_text_content_tip": "請輸入文字", 108 | "dialog_alert_title": "Alert命令: ", 109 | "dialog_alert_option": "Alert選項: ", 110 | "dialog_alert_option_accpet": "接受", 111 | "dialog_alert_option_dismiss": "拒絕", 112 | "dialog_jscode_title": "執行JS", 113 | "dialog_jscode_code": "JS代碼", 114 | "dialog_jscode_tip": "請輸入瀏覽器端執行的JS代碼,例如: document.title='test';" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var initConfig = require('./lib/init.js'); 2 | var startRecorder = require('./lib/start.js'); 3 | var checkUpdate = require('./lib/update.js'); 4 | 5 | module.exports = { 6 | init: initConfig, 7 | start: startRecorder, 8 | checkUpdate: checkUpdate 9 | }; 10 | -------------------------------------------------------------------------------- /lib/builder/java.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | function getJavaTemplateContent(rootPath) { 5 | var templateName = 'javaTemplate.java'; 6 | var tempalteFilePath = path.resolve(__dirname, '../../template/' + templateName); 7 | var customTemplateFilePath = path.join(rootPath, '../template/', templateName); 8 | if (fs.existsSync(customTemplateFilePath)) { 9 | tempalteFilePath = customTemplateFilePath; 10 | } 11 | return fs.readFileSync(tempalteFilePath).toString(); 12 | } 13 | 14 | function getArrRawCmdsTarget(arrRawCmdsTarget, browserSize, arrRawCmds) { 15 | if (browserSize) { 16 | arrRawCmdsTarget[0] = 'Configuration.browserSize = "' + browserSize[0] + 'x' + browserSize[1] + '";'; 17 | } else { 18 | arrRawCmdsTarget[0] = 'Configuration.startMaximized=true;'; 19 | } 20 | for (jj = 0, len = arrRawCmds.length; jj < len; jj++) { 21 | // console.log('arrRawCmds[jj].type:'+arrRawCmds[jj].type); 22 | switch (arrRawCmds[jj].type) { 23 | case 'url': 24 | arrRawCmds[jj].data = arrRawCmds[jj].data.replace(/\"/g, "'"); 25 | arrRawCmdsTarget[jj + 1] = 'actions.openUrl("' + arrRawCmds[jj].data + '");' + '//打开URL'; 26 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 27 | break; 28 | case 'dblClick': 29 | arrRawCmds[jj].data.path = arrRawCmds[jj].data.path.replace(/\"/g, "'"); 30 | arrRawCmdsTarget[jj + 1] = 'actions.click("' + arrRawCmds[jj].data.path + '");' + '//点击:' + arrRawCmds[jj].data.text; 31 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 32 | break; 33 | case 'click': 34 | arrRawCmds[jj].data.path = arrRawCmds[jj].data.path.replace(/\"/g, "'"); 35 | if (arrRawCmds[jj].data.path.indexOf('radio') != -1 || arrRawCmds[jj].data.path.indexOf('checkbox') != -1 || arrRawCmds[jj].data.path.indexOf('combobox') != -1) { 36 | arrRawCmdsTarget[jj + 1] = 'actions.clickByJS("' + arrRawCmds[jj].data.path + '");' + '//点击:' + arrRawCmds[jj].data.text; 37 | } else { 38 | arrRawCmdsTarget[jj + 1] = 'actions.click("' + arrRawCmds[jj].data.path + '");' + '//点击:' + arrRawCmds[jj].data.text; 39 | } 40 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 41 | break; 42 | case 'switchWindow': 43 | //arrRawCmds[jj].path = arrRawCmds[jj].data.replace(/\"/g, "'"); 44 | arrRawCmdsTarget[jj + 1] = 'actions.swtichToWindow(' + arrRawCmds[jj].data + ');' + '//切换窗口'; 45 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 46 | break; 47 | case 'sendKeys': 48 | // arrRawCmds[jj - 1].data.path = arrRawCmds[jj - 1].data.path.replace(/\"/g, "'"); 49 | // arrRawCmdsTarget[jj + 1] = 'actions.input("' + arrRawCmds[jj - 1].data.path + '","' + arrRawCmds[jj].data.keys + '");'+'//文本框'+arrRawCmds[jj - 1].data.text+'输入:'+arrRawCmds[jj].data.keys; 50 | arrRawCmdsTarget[jj + 1] = 'actions.sendKeys("' + arrRawCmds[jj].data.keys + '");' + '//输入:' + arrRawCmds[jj].data.keys; 51 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 52 | break; 53 | case 'waitBody': 54 | arrRawCmdsTarget[jj + 1] = 'actions.sleep(3000);'; 55 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]) + '//waitBody等待页面加载完成'; 56 | break; 57 | // case 'switchFrame': 58 | // arrRawCmds[jj].data = arrRawCmds[jj].data.replace(/\"/g,"'"); 59 | // arrRawCmdsTarget[jj]='actions.switchToFrame("'+arrRawCmds[jj].data+'");'; 60 | // console.log('lion arrRawCmdsTarget['+jj+']:'+arrRawCmdsTarget[jj]); 61 | // break; 62 | case 'switchFrame': 63 | if (null == arrRawCmds[jj].data) { 64 | arrRawCmdsTarget[jj + 1] = 'actions.switchToDefaultContent();' + '//退出frame'; 65 | } else { 66 | arrRawCmds[jj].data = arrRawCmds[jj].data.replace(/#/g, ""); 67 | arrRawCmdsTarget[jj + 1] = 'actions.switchToFrame("' + arrRawCmds[jj].data + '");' + '//切换frame'; 68 | } 69 | console.log('lion arrRawCmdsTarget[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 70 | break; 71 | case 'scrollTo': 72 | arrRawCmdsTarget[jj + 1] = 'actions.scrollTo(' + arrRawCmds[jj].data.x + ',' + arrRawCmds[jj].data.y + ');' + '//滚动页面'; 73 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 74 | break; 75 | case 'scrollElementTo': 76 | arrRawCmdsTarget[jj + 1] = 'actions.scrollElementTo("' + arrRawCmds[jj].data.path + '",' + arrRawCmds[jj].data.x + ',' + arrRawCmds[jj].data.y + ');' + '//滚动元素'; 77 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 78 | break; 79 | case 'closeWindow': 80 | arrRawCmdsTarget[jj + 1] = 'actions.closeCurrentWindow();' + '//关闭当前窗口' 81 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 82 | break; 83 | case 'sleep': 84 | arrRawCmdsTarget[jj + 1] = 'actions.sleep(' + arrRawCmds[jj].data + ');' + '//sleep'; 85 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 86 | break; 87 | case 'mouseMove': 88 | arrRawCmdsTarget[jj + 1] = 'actions.hover("' + arrRawCmds[jj].data.path + '");' + '//鼠标hover:' + arrRawCmds[jj].data.text; 89 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 90 | break; 91 | case 'expect': 92 | switch (arrRawCmds[jj].data.type) { 93 | case 'displayed': 94 | arrRawCmds[jj].data.params[0] = arrRawCmds[jj].data.params[0].replace(/\"/g, "'"); 95 | if ((arrRawCmds[jj].data.compare == 'equal') && (arrRawCmds[jj].data.to == 'true')) { 96 | arrRawCmdsTarget[jj + 1] = 'Assert.assertTrue(actions.waitElementFound("' + arrRawCmds[jj].data.params[0] + '"));' + '//校验元素存在' 97 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 98 | } else { 99 | arrRawCmdsTarget[jj + 1] = 'Assert.assertFalse(actions.waitElementDisapear("' + arrRawCmds[jj].data.params[0] + '"));' + '//校验元素不存在' 100 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 101 | } 102 | break; 103 | case 'text': 104 | arrRawCmds[jj].data.params[0] = arrRawCmds[jj].data.params[0].replace(/\"/g, "'"); 105 | if (arrRawCmds[jj].data.compare == 'equal') { 106 | arrRawCmdsTarget[jj + 1] = 'Assert.assertTrue(actions.getText("' + arrRawCmds[jj].data.params[0] + '").equals("' + arrRawCmds[jj].data.to + '"));' + '//校验元素的文本值满足期望' 107 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 108 | } else { 109 | arrRawCmdsTarget[jj + 1] = 'Assert.assertFalse(actions.getText("' + arrRawCmds[jj].data.params[0] + '").equals("' + arrRawCmds[jj].data.to + '"));' + '//校验元素的文本值不满足期望' 110 | console.log('lion scrollTo[' + jj + 1 + ']:' + arrRawCmdsTarget[jj + 1]); 111 | } 112 | break; 113 | } 114 | break; 115 | } 116 | } 117 | return arrRawCmdsTarget; 118 | } 119 | 120 | module.exports = { 121 | getJavaTemplateContent, 122 | getArrRawCmdsTarget, 123 | }; -------------------------------------------------------------------------------- /lib/i18n.js: -------------------------------------------------------------------------------- 1 | var i18n = require('i18n'); 2 | var path = require('path'); 3 | var osLocale = require('os-locale'); 4 | i18n.configure({ 5 | locales:['en', 'zh-cn', 'zh-tw'], 6 | defaultLocale: 'en', 7 | directory: path.resolve(__dirname, '../i18n/'), 8 | updateFiles: false, 9 | extension: '.js', 10 | register: global 11 | }); 12 | 13 | var loc = osLocale.sync(); 14 | loc = loc.toLowerCase().replace(/_/g, '-'); 15 | i18n.setLocale(loc); 16 | module.exports = i18n; 17 | -------------------------------------------------------------------------------- /lib/init.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-extra') 2 | var path = require('path'); 3 | var cp = require('child_process'); 4 | var inquirer = require('inquirer'); 5 | var npminstall = require('npminstall'); 6 | var i18n = require('./i18n'); 7 | var co = require('co'); 8 | 9 | var projectPath = path.resolve(__dirname, '../project'); 10 | 11 | function initConfig(options){ 12 | var locale = options.locale; 13 | var mobile = options.mobile; 14 | if(locale){ 15 | i18n.setLocale(locale); 16 | } 17 | var configPath = 'config.json'; 18 | var configFile = path.resolve(configPath); 19 | var config = {}; 20 | if(fs.existsSync(configFile)){ 21 | var content = fs.readFileSync(configFile).toString(); 22 | try{ 23 | config = JSON.parse(content); 24 | } 25 | catch(e){} 26 | } 27 | var recorder; 28 | config.webdriver = config.webdriver || {}; 29 | config.vars = config.vars || {}; 30 | config.reporter = config.reporter || { distDir: '' }; 31 | config.screenshots = config.screenshots || { captureAll: true }; 32 | var webdriver = config.webdriver; 33 | webdriver.host = webdriver.host || '127.0.0.1'; 34 | webdriver.port = webdriver.port || '4444'; 35 | webdriver.chromeOptions = webdriver.chromeOptions || { w3c: false }; 36 | if(!mobile){ 37 | config.recorder = config.recorder || {}; 38 | recorder = config.recorder; 39 | recorder.pathAttrs = recorder.pathAttrs || 'data-id,data-name,type,data-type,role,data-role,data-value'; 40 | recorder.attrValueBlack = recorder.attrValueBlack || ''; 41 | recorder.classValueBlack = recorder.classValueBlack || ''; 42 | recorder.hideBeforeExpect = recorder.hideBeforeExpect || ''; 43 | webdriver.browsers = webdriver.browsers || 'chrome, ie 11'; 44 | } 45 | var questions = []; 46 | if(mobile){ 47 | questions = [ 48 | { 49 | 'type': 'input', 50 | 'name': 'host', 51 | 'message': __('webdriver_host'), 52 | 'default': webdriver.host, 53 | 'filter': function(input){ 54 | return input.replace(/(^\s+|\s+$)/g, ''); 55 | }, 56 | 'validate': function(input){ 57 | return input !== '' && /^https?:\/\//.test(input) === false; 58 | } 59 | }, 60 | { 61 | 'type': 'input', 62 | 'name': 'port', 63 | 'message': __('webdriver_port'), 64 | 'default': webdriver.port, 65 | 'filter': function(input){ 66 | return input.replace(/(^\s+|\s+$)/g, ''); 67 | }, 68 | 'validate': function(input){ 69 | return input !== ''; 70 | } 71 | } 72 | ]; 73 | } 74 | else{ 75 | questions = [ 76 | { 77 | 'type': 'input', 78 | 'name': 'pathAttrs', 79 | 'message': __('dom_path_config'), 80 | 'default': recorder.pathAttrs, 81 | 'filter': function(input){ 82 | return input.replace(/(^\s+|\s+$)/g, ''); 83 | } 84 | }, 85 | { 86 | 'type': 'input', 87 | 'name': 'attrValueBlack', 88 | 'message': __('attr_black_list'), 89 | 'default': recorder.attrValueBlack, 90 | 'filter': function(input){ 91 | return input.replace(/(^\s+|\s+$)/g, ''); 92 | } 93 | }, 94 | { 95 | 'type': 'input', 96 | 'name': 'classValueBlack', 97 | 'message': __('class_black_list'), 98 | 'default': recorder.classValueBlack, 99 | 'filter': function(input){ 100 | return input.replace(/(^\s+|\s+$)/g, ''); 101 | } 102 | }, 103 | { 104 | 'type': 'input', 105 | 'name': 'hideBeforeExpect', 106 | 'message': __('hide_before_expect'), 107 | 'default': recorder.hideBeforeExpect, 108 | 'filter': function(input){ 109 | return input.replace(/(^\s+|\s+$)/g, ''); 110 | } 111 | }, 112 | { 113 | 'type': 'input', 114 | 'name': 'host', 115 | 'message': __('webdriver_host'), 116 | 'default': webdriver.host, 117 | 'filter': function(input){ 118 | return input.replace(/(^\s+|\s+$)/g, ''); 119 | }, 120 | 'validate': function(input){ 121 | return input !== '' && /^https?:\/\//.test(input) === false; 122 | } 123 | }, 124 | { 125 | 'type': 'input', 126 | 'name': 'port', 127 | 'message': __('webdriver_port'), 128 | 'default': webdriver.port, 129 | 'filter': function(input){ 130 | return input.replace(/(^\s+|\s+$)/g, ''); 131 | }, 132 | 'validate': function(input){ 133 | return input !== ''; 134 | } 135 | }, 136 | { 137 | 'type': 'input', 138 | 'name': 'browsers', 139 | 'message': __('webdriver_browsers'), 140 | 'default': webdriver.browsers, 141 | 'filter': function(input){ 142 | return input.replace(/(^\s+|\s+$)/g, ''); 143 | }, 144 | 'validate': function(input){ 145 | return input !== ''; 146 | } 147 | } 148 | ]; 149 | } 150 | inquirer.prompt(questions).then(function(anwsers){ 151 | webdriver.host = String(anwsers.host).replace(/^\s+|\s+$/g, ''); 152 | webdriver.port = String(anwsers.port).replace(/^\s+|\s+$/g, ''); 153 | if(!mobile){ 154 | recorder.pathAttrs = String(anwsers.pathAttrs).replace(/^\s+|\s+$/g, ''); 155 | recorder.attrValueBlack = String(anwsers.attrValueBlack).replace(/^\s+|\s+$/g, ''); 156 | recorder.classValueBlack = String(anwsers.classValueBlack).replace(/^\s+|\s+$/g, ''); 157 | recorder.hideBeforeExpect = String(anwsers.hideBeforeExpect).replace(/^\s+|\s+$/g, ''); 158 | webdriver.browsers = String(anwsers.browsers).replace(/^\s+|\s+$/g, ''); 159 | } 160 | fs.writeFileSync(configFile, JSON.stringify(config, null, 4)); 161 | console.log(''); 162 | console.log(configPath.bold+' '+__('file_saved').green); 163 | if(mobile){ 164 | initProject({ 165 | 'package.json':'', 166 | 'README.md':'', 167 | 'screenshots':'', 168 | 'commons':'', 169 | '.editorconfig':'', 170 | '.gitignore1':'.gitignore', 171 | 'install.sh':'', 172 | 'run.bat':'', 173 | 'run.sh':'', 174 | 'vslaunch.json':'.vscode/launch.json' 175 | }); 176 | } 177 | else{ 178 | var hostsPath = 'hosts'; 179 | initProject({ 180 | 'package.json':'', 181 | 'README.md':'', 182 | 'screenshots':'', 183 | 'commons':'', 184 | 'uploadfiles':'', 185 | '.editorconfig':'', 186 | '.gitignore1':'.gitignore', 187 | 'install.sh':'', 188 | 'run.bat':'', 189 | 'run.sh':'', 190 | 'hosts': hostsPath, 191 | 'vslaunch.json':'.vscode/launch.json' 192 | }); 193 | } 194 | co(function* () { 195 | console.log(''); 196 | console.log('Start install project dependencies...'.cyan); 197 | console.log('--------------------------------------------'); 198 | console.log(''); 199 | var npminstallOptions = { 200 | root: process.cwd(), 201 | registry: 'https://registry.npm.taobao.org' 202 | }; 203 | var proxy; 204 | try{ 205 | var cnpmout = cp.execSync('cnpm config get proxy', {stdio:['pipe', 'pipe', 'ignore']}); 206 | cnpmout = cnpmout.toString().trim(); 207 | if(/^http:\/\//.test(cnpmout)){ 208 | proxy = cnpmout; 209 | } 210 | } 211 | catch(e){} 212 | if(proxy){ 213 | npminstallOptions.proxy = proxy; 214 | console.log('Find proxy from cnpm:', proxy); 215 | } 216 | yield npminstall(npminstallOptions); 217 | if(!mobile){ 218 | console.log(''); 219 | console.log('Start install webdriver dependencies...'.cyan); 220 | console.log('--------------------------------------------'); 221 | console.log(''); 222 | var installdriver = cp.exec('npm run installdriver'); 223 | installdriver.stdout.on('data', function(data){ 224 | console.log(data); 225 | }); 226 | } 227 | }); 228 | }).catch(function(err){ 229 | console.log(err) 230 | }); 231 | 232 | } 233 | 234 | function initProject(mapFiles){ 235 | for(var key in mapFiles){ 236 | initProjectFileOrDir(key, mapFiles[key]); 237 | } 238 | } 239 | 240 | function initProjectFileOrDir(srcName, descName){ 241 | descName = descName || srcName; 242 | var srcFile = projectPath + '/' + srcName; 243 | var destFile = path.resolve(descName); 244 | if(fs.existsSync(destFile) === false){ 245 | fs.copySync(srcFile, destFile); 246 | console.log(descName.bold+' '+__(fs.statSync(srcFile).isDirectory()?'dir_created':'file_created').green); 247 | } 248 | } 249 | 250 | module.exports = initConfig; 251 | -------------------------------------------------------------------------------- /lib/update.js: -------------------------------------------------------------------------------- 1 | var latestVersion = require('latest-version'); 2 | var semver = require('semver'); 3 | var pkg = require('../package.json'); 4 | var i18n = require('./i18n'); 5 | 6 | function checkUpdate(locale){ 7 | if(locale){ 8 | i18n.setLocale(locale); 9 | } 10 | var pkgName = pkg.name; 11 | latestVersion(pkgName).then(function(newVersion){ 12 | var oldVersion = pkg.version; 13 | var isNew = semver.gt(newVersion, oldVersion) 14 | if(isNew){ 15 | process.on('exit', function(){ 16 | var updateCmd = 'npm update '+pkgName+' -g'; 17 | console.log(''); 18 | console.log('------------------------------------------------------------------'.yellow); 19 | console.log(''); 20 | console.log(__('update_tip', oldVersion.gray, newVersion.green.bold, updateCmd.cyan.bold)); 21 | console.log(''); 22 | console.log('------------------------------------------------------------------'.yellow); 23 | }); 24 | process.on('SIGINT', function () { 25 | console.error(''); 26 | process.exit(); 27 | }); 28 | } 29 | }); 30 | } 31 | 32 | module.exports = checkUpdate; 33 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uirecorder", 3 | "version": "4.0.2-beta.2", 4 | "description": "Tool for record ui test case", 5 | "main": "./index", 6 | "bin": { 7 | "uirecorder": "./bin/uirecorder" 8 | }, 9 | "dependencies": { 10 | "jquery": "3.x", 11 | "async": "2.1.2", 12 | "chai": "3.5.0", 13 | "chromedriver": "latest", 14 | "co": "4.6.0", 15 | "colors": "1.1.2", 16 | "commander": "2.9.0", 17 | "detect-port": "^1.3.0", 18 | "fs-extra": "1.0.0", 19 | "i18n": "0.8.3", 20 | "inquirer": "3.0.1", 21 | "jwebdriver": "2.2.6", 22 | "latest-version": "2.0.0", 23 | "npminstall": "3.1.1", 24 | "os-locale": "1.4.0", 25 | "resemblejs-node": "1.0.0", 26 | "semver": "5.3.0", 27 | "websocket": "1.0.22" 28 | }, 29 | "devDependencies": { 30 | "git-contributor": "^1.0.8" 31 | }, 32 | "scripts": { 33 | "contributor": "git-contributor" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git@github.com:alibaba/uirecorder.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/alibaba/uirecorder/issues" 41 | }, 42 | "keywords": [ 43 | "uirecorder", 44 | "webdriver", 45 | "test", 46 | "recorder" 47 | ], 48 | "author": "Yanis Wang, Stngle", 49 | "license": "MIT" 50 | } 51 | -------------------------------------------------------------------------------- /project/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | # Apply for all files 8 | [*] 9 | 10 | charset = utf-8 11 | 12 | indent_style = space 13 | indent_size = 4 14 | 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | -------------------------------------------------------------------------------- /project/.gitignore1: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | npm-debug.log 5 | uirecorder.log 6 | reports 7 | screenshots/**/*.png 8 | screenshots/**/*.html 9 | screenshots/**/*.json 10 | -------------------------------------------------------------------------------- /project/README.md: -------------------------------------------------------------------------------- 1 | UI Recorder test sample project 2 | ================ 3 | 4 | It's a UI Recorder test sample project. 5 | 6 | Save your test code here. 7 | 8 | Get more info: [http://uirecorder.com/](http://uirecorder.com/) 9 | 10 | How to run test case? 11 | ================ 12 | 13 | 1. npm install 14 | 2. source run.sh ( Linux|Mac ) or run.bat ( Windows ) 15 | 16 | How to dock jenkins? 17 | ================ 18 | 19 | 1. Add commands 20 | 21 | source ./install.sh 22 | source ./run.sh 23 | 24 | 2. Add reports 25 | 26 | > JUnit: reports/index.xml 27 | 28 | > HTML: reports/ 29 | -------------------------------------------------------------------------------- /project/commons/commons.md: -------------------------------------------------------------------------------- 1 | Please save common test case here. -------------------------------------------------------------------------------- /project/hosts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/project/hosts -------------------------------------------------------------------------------- /project/install.sh: -------------------------------------------------------------------------------- 1 | ls ~/nvm || git clone https://github.com/creationix/nvm.git ~/nvm 2 | source ~/nvm/nvm.sh 3 | nvm install v7.10.0 4 | npm install 5 | -------------------------------------------------------------------------------- /project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uirecorderTest", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "", 6 | "dependencies": { 7 | "chai": "3.5.0", 8 | "jwebdriver": "2.3.4", 9 | "mocha": "5", 10 | "macaca-mocha-parallel-tests": "2.x", 11 | "macaca-reporter": "^1.3.24", 12 | "resemblejs-node": "1.0.0", 13 | "selenium-standalone": "6.x.x", 14 | "jquery": "3.x" 15 | }, 16 | "devDependencies": { 17 | }, 18 | "scripts": { 19 | "installdriver": "selenium-standalone install --drivers.firefox.baseURL=http://npm.taobao.org/mirrors/geckodriver --baseURL=http://npm.taobao.org/mirrors/selenium --drivers.chrome.baseURL=http://npm.taobao.org/mirrors/chromedriver --drivers.ie.baseURL=http://npm.taobao.org/mirrors/selenium", 20 | "server": "selenium-standalone start", 21 | "test": "mocha \"!(node_modules)/**/*.spec.js\" --reporter macaca-reporter --reporter-options reportJSONFilename=index,processAlwaysExitWithZero=true --bail", 22 | "singletest": "mocha --reporter macaca-reporter --reporter-options reportJSONFilename=index,processAlwaysExitWithZero=true --bail", 23 | "paralleltest": "macaca-mocha-parallel-tests \"!(node_modules)/**/*.spec.js\" --reporter macaca-reporter --reporter-options reportJSONFilename=index,processAlwaysExitWithZero=true --max-parallel 5 --bail", 24 | "moduletest": "macaca-mocha-parallel-tests --reporter macaca-reporter --reporter-options reportJSONFilename=index,processAlwaysExitWithZero=true --max-parallel 5 --bail" 25 | }, 26 | "author": "" 27 | } 28 | -------------------------------------------------------------------------------- /project/run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if "%1" neq "" ( 4 | npm run singletest %1 %2 5 | ) else ( 6 | npm run paralleltest 7 | ) 8 | -------------------------------------------------------------------------------- /project/run.sh: -------------------------------------------------------------------------------- 1 | if [ "$1" = "" ]; then 2 | npm run paralleltest 3 | else 4 | npm run singletest $1 $2 5 | fi 6 | -------------------------------------------------------------------------------- /project/screenshots/screenshots.md: -------------------------------------------------------------------------------- 1 | Test screenshots saved here. -------------------------------------------------------------------------------- /project/uploadfiles/uploadfiles.md: -------------------------------------------------------------------------------- 1 | Please save upload files here. 2 | -------------------------------------------------------------------------------- /project/vslaunch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug UIRecorder Local", 11 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 12 | "cwd": "${workspaceRoot}", 13 | "args": ["--reporter", "macaca-reporter", "${file}"], 14 | "env": { 15 | "webdriver": "127.0.0.1" 16 | } 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Debug UIRecorder Default", 22 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 23 | "cwd": "${workspaceRoot}", 24 | "args": ["--reporter", "macaca-reporter", "${file}"] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /screenshot/shot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/screenshot/shot1.png -------------------------------------------------------------------------------- /screenshot/shot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/screenshot/shot2.png -------------------------------------------------------------------------------- /screenshot/shot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/screenshot/shot3.png -------------------------------------------------------------------------------- /screenshot/shot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/screenshot/shot4.png -------------------------------------------------------------------------------- /template/javaTemplate.java: -------------------------------------------------------------------------------- 1 | package uitest.tc; 2 | 3 | import org.testng.annotations.Test; 4 | import uitest.BaseTest; 5 | import org.testng.Assert; 6 | import com.codeborne.selenide.Configuration; 7 | 8 | public class JavaUITest extends BaseTest { 9 | @Test 10 | public void test(){ 11 | {$testCodes} 12 | //java code ending 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /template/jwebdriver-mobile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const cp = require('child_process'); 4 | const chai = require("chai"); 5 | const should = chai.should(); 6 | const JWebDriver = require('jwebdriver'); 7 | chai.use(JWebDriver.chaiSupportChainPromise); 8 | const resemble = require('resemblejs-node'); 9 | resemble.outputSettings({ 10 | errorType: 'flatDifferenceIntensity' 11 | }); 12 | 13 | const rootPath = getRootPath(); 14 | const appPath = '{$appPath}'; 15 | const platformName = '{$platformName}'; 16 | 17 | module.exports = function(){ 18 | 19 | var driver, testVars; 20 | 21 | before(function(){ 22 | var self = this; 23 | driver = self.driver; 24 | testVars = self.testVars; 25 | }); 26 | 27 | {$testCodes} 28 | function _(str){ 29 | if(typeof str === 'string'){ 30 | return str.replace(/\{\{(.+?)\}\}/g, function(all, key){ 31 | return testVars[key] || ''; 32 | }); 33 | } 34 | else{ 35 | return str; 36 | } 37 | } 38 | 39 | }; 40 | 41 | if(module.parent && /mocha\.js/.test(module.parent.id)){ 42 | runThisSpec(); 43 | } 44 | 45 | function runThisSpec(){ 46 | // read config 47 | let config = require(rootPath + '/config.json'); 48 | let webdriverConfig = Object.assign({},config.webdriver); 49 | let host = webdriverConfig.host; 50 | let port = webdriverConfig.port || 4444; 51 | let testVars = config.vars; 52 | 53 | let specName = path.relative(rootPath, __filename).replace(/\\/g,'/').replace(/\.js$/,''); 54 | 55 | let arrDeviceList = getEnvList() || getDeviceList(platformName); 56 | if(arrDeviceList.length ===0 ){ 57 | console.log('Search mobile device failed!'); 58 | process.exit(1); 59 | } 60 | 61 | arrDeviceList.forEach(function(device){ 62 | let caseName = specName + ' : ' + (device.name?device.name+' ['+device.udid+']':device.udid); 63 | 64 | describe(caseName, function(){ 65 | 66 | this.timeout(600000); 67 | this.slow(1000); 68 | 69 | before(function(){ 70 | let self = this; 71 | let driver = new JWebDriver({ 72 | 'host': host, 73 | 'port': port 74 | }); 75 | self.driver = driver.session({ 76 | 'platformName': platformName, 77 | 'udid': device.udid, 78 | 'app': /^(\/|[a-z]:\\|https?:\/\/)/i.test(appPath) ? appPath : rootPath + '/' + appPath 79 | }); 80 | self.testVars = testVars; 81 | let casePath = path.dirname(caseName); 82 | self.screenshotPath = rootPath + '/screenshots/' + casePath; 83 | self.diffbasePath = rootPath + '/diffbase/' + casePath; 84 | self.caseName = caseName.replace(/.*\//g, '').replace(/\s*[:\.\:\-\s]\s*/g, '_'); 85 | mkdirs(self.screenshotPath); 86 | mkdirs(self.diffbasePath); 87 | self.stepId = 0; 88 | return self.driver; 89 | }); 90 | 91 | module.exports(); 92 | 93 | beforeEach(function(){ 94 | let self = this; 95 | self.stepId ++; 96 | }); 97 | 98 | afterEach(async function(){ 99 | let self = this; 100 | let filepath = self.screenshotPath + '/' + self.caseName + '_' + self.stepId; 101 | let driver = self.driver; 102 | await driver.getScreenshot(filepath + '.png'); 103 | let json = await driver.source(); 104 | fs.writeFileSync(filepath + '.json', json); 105 | }); 106 | 107 | after(function(){ 108 | return this.driver.close(); 109 | }); 110 | 111 | }); 112 | }); 113 | } 114 | 115 | function getRootPath(){ 116 | let rootPath = path.resolve(__dirname); 117 | while(rootPath){ 118 | if(fs.existsSync(rootPath + '/config.json')){ 119 | break; 120 | } 121 | rootPath = rootPath.substring(0, rootPath.lastIndexOf(path.sep)); 122 | } 123 | return rootPath; 124 | } 125 | 126 | function mkdirs(dirname){ 127 | if(fs.existsSync(dirname)){ 128 | return true; 129 | }else{ 130 | if(mkdirs(path.dirname(dirname))){ 131 | fs.mkdirSync(dirname); 132 | return true; 133 | } 134 | } 135 | } 136 | 137 | function callSpec(name){ 138 | try{ 139 | require(rootPath + '/' + name)(); 140 | } 141 | catch(e){ 142 | console.log(e) 143 | process.exit(1); 144 | } 145 | } 146 | 147 | function getEnvList(){ 148 | let strEnvList = process.env.devices; 149 | if(strEnvList){ 150 | return strEnvList.split(/\s*,\s*/).map(function(udid){ 151 | return {udid: udid}; 152 | }); 153 | } 154 | } 155 | 156 | function getDeviceList(platformName){ 157 | let arrDeviceList = []; 158 | let strText, match; 159 | if(platformName === 'Android') 160 | { 161 | // for android 162 | strText = cp.execSync('adb devices').toString(); 163 | strText.replace(/(.+?)\s+device\r?\n/g, function(all, deviceName){ 164 | arrDeviceList.push({ 165 | udid: deviceName 166 | }); 167 | }); 168 | } 169 | else{ 170 | // ios real device 171 | strText = cp.execSync('idevice_id -l').toString(); 172 | strText.replace(/(.+)\r?\n/g, function(all, udid){ 173 | let deviceName = cp.execSync('idevice_id '+udid).toString(); 174 | deviceName = deviceName.replace(/\r?\n/g, ''); 175 | arrDeviceList.push({ 176 | name: deviceName, 177 | udid: udid 178 | }); 179 | }); 180 | // ios simulator 181 | strText = cp.execSync('xcrun simctl list devices').toString(); 182 | strText.replace(/\r?\n\s*(.+?)\s+\((.+?)\) \(Booted\)/g, function(all, deviceName, udid){ 183 | arrDeviceList.push({ 184 | name: deviceName, 185 | udid: udid 186 | }); 187 | }); 188 | } 189 | return arrDeviceList; 190 | } 191 | -------------------------------------------------------------------------------- /template/jwebdriver.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const chai = require("chai"); 5 | const should = chai.should(); 6 | const JWebDriver = require('jwebdriver'); 7 | chai.use(JWebDriver.chaiSupportChainPromise); 8 | const resemble = require('resemblejs-node'); 9 | resemble.outputSettings({ 10 | errorType: 'flatDifferenceIntensity' 11 | }); 12 | 13 | const rootPath = getRootPath(); 14 | 15 | module.exports = function(){ 16 | 17 | let driver, testVars; 18 | 19 | before(function(){ 20 | let self = this; 21 | driver = self.driver; 22 | testVars = self.testVars; 23 | }); 24 | 25 | {$testCodes} 26 | function _(str){ 27 | if(typeof str === 'string'){ 28 | return str.replace(/\{\{(.+?)\}\}/g, function(all, key){ 29 | return testVars[key] || ''; 30 | }); 31 | } 32 | else{ 33 | return str; 34 | } 35 | } 36 | 37 | }; 38 | 39 | if(module.parent && /mocha\.js/.test(module.parent.id)){ 40 | runThisSpec(); 41 | } 42 | 43 | function runThisSpec(){ 44 | // read config 45 | let webdriver = process.env['webdriver'] || ''; 46 | let proxy = process.env['wdproxy'] || ''; 47 | let config = require(rootPath + '/config.json'); 48 | let webdriverConfig = Object.assign({},config.webdriver); 49 | let host = webdriverConfig.host; 50 | let port = webdriverConfig.port || 4444; 51 | let group = webdriverConfig.group || 'default'; 52 | let match = webdriver.match(/([^\:]+)(?:\:(\d+))?/); 53 | if(match){ 54 | host = match[1] || host; 55 | port = match[2] || port; 56 | } 57 | let testVars = config.vars; 58 | let browsers = webdriverConfig.browsers; 59 | browsers = browsers.replace(/^\s+|\s+$/g, ''); 60 | delete webdriverConfig.host; 61 | delete webdriverConfig.port; 62 | delete webdriverConfig.group; 63 | delete webdriverConfig.browsers; 64 | 65 | // read hosts 66 | let hostsPath = rootPath + '/hosts'; 67 | let hosts = ''; 68 | if(fs.existsSync(hostsPath)){ 69 | hosts = fs.readFileSync(hostsPath).toString(); 70 | } 71 | let specName = path.relative(rootPath, __filename).replace(/\\/g,'/').replace(/\.js$/,''); 72 | 73 | browsers.split(/\s*,\s*/).forEach(function(browserName){ 74 | let caseName = specName + ' : ' + browserName; 75 | 76 | let browserInfo = browserName.split(' '); 77 | browserName = browserInfo[0]; 78 | let browserVersion = browserInfo[1]; 79 | 80 | describe(caseName, function(){ 81 | 82 | this.timeout(600000); 83 | this.slow(1000); 84 | 85 | let driver; 86 | before(function(){ 87 | let self = this; 88 | let driver = new JWebDriver({ 89 | 'host': host, 90 | 'port': port 91 | }); 92 | let sessionConfig = Object.assign({}, webdriverConfig, { 93 | 'group': group, 94 | 'browserName': browserName, 95 | 'version': browserVersion, 96 | 'ie.ensureCleanSession': true, 97 | }); 98 | if(proxy){ 99 | sessionConfig.proxy = { 100 | 'proxyType': 'manual', 101 | 'httpProxy': proxy, 102 | 'sslProxy': proxy 103 | } 104 | } 105 | else if(hosts){ 106 | sessionConfig.hosts = hosts; 107 | } 108 | 109 | try { 110 | self.driver = driver.session(sessionConfig){$sizeCode}.config({ 111 | pageloadTimeout: 30000, // page onload timeout 112 | scriptTimeout: 5000, // sync script timeout 113 | asyncScriptTimeout: 10000 // async script timeout 114 | }); 115 | } catch (e) { 116 | console.log(e); 117 | } 118 | 119 | self.testVars = testVars; 120 | let casePath = path.dirname(caseName); 121 | if (config.reporter && config.reporter.distDir) { 122 | self.screenshotPath = config.reporter.distDir + '/reports/screenshots/' + casePath; 123 | self.diffbasePath = config.reporter.distDir + '/reports/diffbase/' + casePath; 124 | } else { 125 | self.screenshotPath = rootPath + '/reports/screenshots/' + casePath; 126 | self.diffbasePath = rootPath + '/reports/diffbase/' + casePath; 127 | } 128 | self.caseName = caseName.replace(/.*\//g, '').replace(/\s*[:\.\:\-\s]\s*/g, '_'); 129 | mkdirs(self.screenshotPath); 130 | mkdirs(self.diffbasePath); 131 | self.stepId = 0; 132 | return self.driver; 133 | }); 134 | 135 | module.exports(); 136 | 137 | beforeEach(function(){ 138 | let self = this; 139 | self.stepId ++; 140 | if(self.skipAll){ 141 | self.skip(); 142 | } 143 | }); 144 | 145 | afterEach(async function(){ 146 | let self = this; 147 | let currentTest = self.currentTest; 148 | let title = currentTest.title; 149 | if(currentTest.state === 'failed' && /^(url|waitBody|switchWindow|switchFrame):/.test(title)){ 150 | self.skipAll = true; 151 | } 152 | 153 | if ((config.screenshots && config.screenshots.captureAll && !/^(closeWindow):/.test(title)) || currentTest.state === 'failed') { 154 | const casePath = path.dirname(caseName); 155 | const filepath = `${self.screenshotPath}/${self.caseName}_${self.stepId}`; 156 | const relativeFilePath = `./screenshots/${casePath}/${self.caseName}_${self.stepId}`; 157 | let driver = self.driver; 158 | try{ 159 | // catch error when get alert msg 160 | await driver.getScreenshot(filepath + '.png'); 161 | let url = await driver.url(); 162 | let html = await driver.source(); 163 | html = '\n' + html; 164 | fs.writeFileSync(filepath + '.html', html); 165 | let cookies = await driver.cookies(); 166 | fs.writeFileSync(filepath + '.cookie', JSON.stringify(cookies)); 167 | appendToContext(self, relativeFilePath + '.png'); 168 | } 169 | catch(e){} 170 | } 171 | }); 172 | 173 | after(function(){ 174 | return this.driver.close(); 175 | }); 176 | 177 | }); 178 | }); 179 | } 180 | 181 | function getRootPath(){ 182 | let rootPath = path.resolve(__dirname); 183 | while(rootPath){ 184 | if(fs.existsSync(rootPath + '/config.json')){ 185 | break; 186 | } 187 | rootPath = rootPath.substring(0, rootPath.lastIndexOf(path.sep)); 188 | } 189 | return rootPath; 190 | } 191 | 192 | function mkdirs(dirname){ 193 | if(fs.existsSync(dirname)){ 194 | return true; 195 | }else{ 196 | if(mkdirs(path.dirname(dirname))){ 197 | fs.mkdirSync(dirname); 198 | return true; 199 | } 200 | } 201 | } 202 | 203 | function callSpec(name){ 204 | try{ 205 | require(rootPath + '/' + name)(); 206 | } 207 | catch(e){ 208 | console.log(e) 209 | process.exit(1); 210 | } 211 | } 212 | 213 | function isPageError(code){ 214 | return code == '' || / jscontent="errorCode" jstcache="\d+"|diagnoseConnectionAndRefresh|dnserror_unavailable_header|id="reportCertificateErrorRetry"|400 Bad Request|403 Forbidden|404 Not Found|500 Internal Server Error|502 Bad Gateway|503 Service Temporarily Unavailable|504 Gateway Time-out/i.test(code); 215 | } 216 | 217 | function appendToContext(mocha, content) { 218 | try { 219 | const test = mocha.currentTest || mocha.test; 220 | 221 | if (!test.context) { 222 | test.context = content; 223 | } else if (Array.isArray(test.context)) { 224 | test.context.push(content); 225 | } else { 226 | test.context = [test.context]; 227 | test.context.push(content); 228 | } 229 | } catch (e) { 230 | console.log('error', e); 231 | } 232 | }; 233 | 234 | function catchError(error){ 235 | 236 | } 237 | -------------------------------------------------------------------------------- /tool/uirecorder.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/uirecorder/72d08a133545b0cb34b424aa835529fd813d673d/tool/uirecorder.crx -------------------------------------------------------------------------------- /uirecorder.log: -------------------------------------------------------------------------------- 1 | __ ______ ____ __ 2 | / / / / _/ / __ \___ _________ _________/ /__ _____ 3 | / / / // / / /_/ / _ \/ ___/ __ \/ ___/ __ / _ \/ ___/ 4 | / /_/ // / / _, _/ __/ /__/ /_/ / / / /_/ / __/ / 5 | \____/___/ /_/ |_|\___/\___/\____/_/ \__,_/\___/_/ v3.3.0 6 | 7 | Official Site: http://uirecorder.com 8 | ------------------------------------------------------------------ 9 | 10 | config.json 文件查找失败,请先初始化! 11 | -------------------------------------------------------------------------------- /uirecorder.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCxTFJyhnu84tZq 3 | /RuCrmdiUK6AcdhiucN0Sn7b7Bts+cL9ncbp7HWWp7bNkNM4Gue0F2ZPuN1eAYVz 4 | d3zSuv450SvnaFQQjoVmFj4ysHw/78iLMpSMiXY+SA/4mwPHrOK16UctcyIJoTDL 5 | e57HsEVKFFwVRKV36Di2Z7gd6dtCmXCvrQR7Xmos5CblxAWSI7hI4/mKxMrnH0A7 6 | Swq0stJ4IrlJel8GEDrWYMH+roWJeJqlgI0f0PrhBWWw1QpnHub/Yc4N2GYIqqoY 7 | xHY3tm0Q5B1bkvC5tkwsO+Y+sfbjtUfHoQX1e4ksT1W9ZruA1zppfjj1PgcGNj7+ 8 | kptfDFi/AgMBAAECggEAakR1smE670cC/5N/lr9UBhCX1zLlYJ85MI2qJcUJ1zKI 9 | lhyoafMps8gIgIPKpfkyYbYYw7XpMPw2cbPvpBsiX6Mo7oWQxW+3My4nz5gKkQP2 10 | rr/9W5LUxZXJxNec12SfaitNV0eH4j+0EHKjA8t6bGFxo+nGR+1veJ0INR3DJtHz 11 | 0gzsKnKd/Ta6z3fVuwKB8wOcYEuIZ5uhJFZWCMyheCcPeKqAkrV15SfP05wDpeko 12 | zvPf0E+IOvC3X55v0MeKQoML+mdKjxGhKrQrVHWMcq0CVn/JWB3U6VCB95t8lDFs 13 | R1fajIFQagBWG4WwQHkklou3+PkB0MChVLZWSNu2MQKBgQDZII99deSgLnF5ATOQ 14 | kaKQ1Jas1CDi0TuzEDaDZ+J8KcY8z/j3Z8n8fTPqtjmJecH9IL0sOtAwqrf1+Nlx 15 | GmlX/5T4fBDeO+WJp5riGeJUtdM4u9WgLZK+gWMegq2ysnGePWizhW2BBRg5xGUk 16 | QBw1ZR2e01iSz+GXKAgbq0+diQKBgQDRCk3ZxGWdmamCAvgVupSbqiZEFC5zNMKn 17 | jnGpoamn96QDrLKlkaGDIf8T4hsyO32B5EYnLxt3sgmQlsd4zBdNb7eSd0q/NnvB 18 | Unbnf9IzAmdcylJc3zmOGmKQWdo6xU7KXJ5hmCEvryNz7dL5pCJNjGa2N4AyzRvE 19 | VCrsxz46BwKBgGTZYsx7Pb3I1JvHmxPDEScEFxgfT0cKuBfrp+ZREjlpjdIhJxqC 20 | 8qZ74Olbyk24aAoScstgZeK06M0u0JBgHB3rcF4aAhu25l6RorbyHtYJvhnT5N2J 21 | TWd+4XMCb3tYtr0w+LipeLs8iowKVJAJ1xBV7vQeZj2KoNV8mod/gnNBAoGAM1Cs 22 | O8ESkNWf3uKLtAnRYUUrj5rErFNPVYKKNHITC8Cm6qACWtKdK2u1ClR/CJ3B+Zjn 23 | /8Z4n7F815mr7eNr9P5vuey+1KGzT4nG1p1yJEN6zDR+c334ywF/IKBuCe9VoCeM 24 | WbjWrLX5pgPDvrSkFxVYQXLubYocPt3Ki8V9aRECgYAc9pbtUAy2M+iHdsT65h9O 25 | q0v25qVq6R7pZCFOjAPFVcUCyN5nqEw9eR5kjMfKd2z3IWpSn2+6gzexnH7bLzFz 26 | 4TleaeyL31BrhkJo2OpJCq1yQa4zhEnn1nlaxqar726NaRl4wF69cv63AdI2eg5x 27 | PWT96evUQ38+4D63THSVew== 28 | -----END PRIVATE KEY----- 29 | --------------------------------------------------------------------------------