├── .eslintignore ├── .eslintrc.js ├── .github ├── semantic.yml └── workflows │ ├── ci.yml │ └── docs-build.yml ├── .gitignore ├── README.md ├── docs ├── .vuepress │ ├── config.js │ ├── public │ │ └── logo │ │ │ └── macaca.svg │ └── styles │ │ └── palette.styl ├── README.md ├── apis │ ├── README.md │ ├── alert │ │ ├── acceptAlert.md │ │ ├── alertKeys.md │ │ ├── alertText.md │ │ └── dismissAlert.md │ ├── assert │ │ ├── hasElement.md │ │ ├── hasElementById.md │ │ ├── hasElementByName.md │ │ ├── hasElementByPartialLinkText.md │ │ ├── hasElementByTagName.md │ │ └── hasElementByXPath.md │ ├── browser │ │ ├── back.md │ │ ├── click.md │ │ ├── forward.md │ │ ├── get.md │ │ ├── maximize.md │ │ └── refresh.md │ ├── context │ │ ├── context.md │ │ ├── contexts.md │ │ └── currentContext.md │ ├── cookie │ │ ├── allCookies.md │ │ ├── deleteAllCookies.md │ │ ├── deleteCookie.md │ │ └── setCookie.md │ ├── element │ │ ├── clear.md │ │ ├── element.md │ │ ├── elementByCss.md │ │ ├── elementByCssIfExists.md │ │ ├── elementByCssOrNull.md │ │ ├── elementById.md │ │ ├── elementByIdIfExists.md │ │ ├── elementByIdOrNull.md │ │ ├── elementByName.md │ │ ├── elementByNameIfExists.md │ │ ├── elementByNameOrNull.md │ │ ├── elementByPartialLinkText.md │ │ ├── elementByPartialLinkTextIfExists.md │ │ ├── elementByPartialLinkTextOrNull.md │ │ ├── elementByTagName.md │ │ ├── elementByTagNameIfExists.md │ │ ├── elementByTagNameOrNull.md │ │ ├── elementByXPath.md │ │ ├── elementByXPathIfExists.md │ │ ├── elementByXPathOrNull.md │ │ ├── elementIfExists.md │ │ ├── elementOrNull.md │ │ ├── elements.md │ │ ├── elementsByCss.md │ │ ├── elementsById.md │ │ ├── elementsByName.md │ │ ├── elementsByPartialLinkText.md │ │ ├── elementsByTagName.md │ │ ├── elementsByXPath.md │ │ ├── getComputedCss.md │ │ ├── getProperty.md │ │ ├── getRect.md │ │ ├── isDisplayed.md │ │ ├── sendKeys.md │ │ ├── takeElementScreenshot.md │ │ ├── text.md │ │ └── waitForElement.md │ ├── execute │ │ └── execute.md │ ├── next │ │ ├── fileChooser.md │ │ ├── frame.md │ │ ├── keys.md │ │ ├── sleep.md │ │ └── touch.md │ ├── screenshot │ │ ├── saveScreenshot.md │ │ └── takeScreenshot.md │ ├── session │ │ ├── init.md │ │ ├── quit.md │ │ ├── sessions.md │ │ └── status.md │ ├── source │ │ └── source.md │ ├── title │ │ └── title.md │ ├── url │ │ └── url.md │ └── window │ │ ├── close.md │ │ ├── getWindowSize.md │ │ ├── setWindowSize.md │ │ ├── window.md │ │ ├── windowHandle.md │ │ └── windowHandles.md └── zh │ ├── README.md │ └── apis │ ├── README.md │ ├── alert │ ├── acceptAlert.md │ ├── alertKeys.md │ ├── alertText.md │ └── dismissAlert.md │ ├── assert │ ├── hasElement.md │ ├── hasElementById.md │ ├── hasElementByName.md │ ├── hasElementByPartialLinkText.md │ ├── hasElementByTagName.md │ └── hasElementByXPath.md │ ├── browser │ ├── back.md │ ├── click.md │ ├── forward.md │ ├── get.md │ ├── maximize.md │ └── refresh.md │ ├── context │ ├── context.md │ ├── contexts.md │ └── currentContext.md │ ├── cookie │ ├── allCookies.md │ ├── deleteAllCookies.md │ ├── deleteCookie.md │ └── setCookie.md │ ├── element │ ├── clear.md │ ├── element.md │ ├── elementByCss.md │ ├── elementByCssIfExists.md │ ├── elementByCssOrNull.md │ ├── elementById.md │ ├── elementByIdIfExists.md │ ├── elementByIdOrNull.md │ ├── elementByName.md │ ├── elementByNameIfExists.md │ ├── elementByNameOrNull.md │ ├── elementByPartialLinkText.md │ ├── elementByPartialLinkTextIfExists.md │ ├── elementByPartialLinkTextOrNull.md │ ├── elementByTagName.md │ ├── elementByTagNameIfExists.md │ ├── elementByTagNameOrNull.md │ ├── elementByXPath.md │ ├── elementByXPathIfExists.md │ ├── elementByXPathOrNull.md │ ├── elementIfExists.md │ ├── elementOrNull.md │ ├── elements.md │ ├── elementsByCss.md │ ├── elementsById.md │ ├── elementsByName.md │ ├── elementsByPartialLinkText.md │ ├── elementsByTagName.md │ ├── elementsByXPath.md │ ├── getComputedCss.md │ ├── getProperty.md │ ├── getRect.md │ ├── isDisplayed.md │ ├── sendKeys.md │ ├── takeElementScreenshot.md │ ├── text.md │ └── waitForElement.md │ ├── execute │ └── execute.md │ ├── next │ ├── fileChooser.md │ ├── frame.md │ ├── keys.md │ ├── sleep.md │ └── touch.md │ ├── screenshot │ ├── saveScreenshot.md │ └── takeScreenshot.md │ ├── session │ ├── init.md │ ├── quit.md │ ├── sessions.md │ └── status.md │ ├── source │ └── source.md │ ├── title │ └── title.md │ ├── url │ └── url.md │ └── window │ ├── close.md │ ├── getWindowSize.md │ ├── setWindowSize.md │ ├── window.md │ ├── windowHandle.md │ └── windowHandles.md ├── lib ├── helper │ ├── assert.js │ ├── coverage.js │ ├── element.js │ ├── index.js │ ├── macaca-datahub.js │ └── reporter.js ├── macaca-wd.js ├── next │ └── index.js └── web │ └── react-router-helper.js ├── package.json ├── test ├── alert.test.js ├── assert.test.js ├── browser.test.js ├── context.test.js ├── cookie.test.js ├── datahub.test.js ├── element.test.js ├── excecute.test.js ├── helper.js ├── mocha.opts ├── others.test.js ├── screenshot.test.js ├── session.test.js ├── source.test.js ├── title.test.js ├── url.test.js ├── utility.test.js └── window.test.js └── wd ├── .gitignore ├── LICENSE.APACHE2 ├── browser-scripts ├── safe-execute-async.js ├── safe-execute.js └── wait-for-cond-in-browser.js ├── doc ├── deprecated-chain.md ├── jsonwire-full-mapping.md ├── jsonwire-full.json ├── jsonwire-mobile.md ├── jsonwire-unsupported-mapping.md ├── mapping-builder.js ├── mapping-template.htm └── release-notes.md ├── lib ├── actions.js ├── asserters.js ├── bin.js ├── callbacks.js ├── commands.js ├── config.js ├── deprecated-chain.js ├── element-commands.js ├── element.js ├── http-utils.js ├── jsonwire-errors.js ├── lodash.js ├── main.js ├── promise-webdriver.js ├── tmp.js ├── utils.js └── webdriver.js └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/node_modules 3 | **/build 4 | **/wd/doc 5 | **/logs 6 | **/theme 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | const eslintConfig = { 5 | extends: 'eslint-config-egg', 6 | env: { 7 | browser: true, 8 | es6: true, 9 | node: true, 10 | }, 11 | settings: { 12 | 'import/resolver': { 13 | alias: { 14 | map: [ 15 | [ '@', `${__dirname}/src` ], 16 | ], 17 | extensions: [ '.js', '.jsx', '.json' ], 18 | }, 19 | }, 20 | }, 21 | parserOptions: { 22 | ecmaVersion: 2020, 23 | ecmaFeatures: { 24 | experimentalObjectRestSpread: true, 25 | }, 26 | }, 27 | plugins: [ 28 | 'import' 29 | ], 30 | ignorePatterns: [ '*.d.ts' ], 31 | rules: { 32 | 'import/extensions': 0, 33 | 'jsdoc/require-param-type': 0, 34 | 'jsdoc/require-param-description': 0, 35 | 'jsdoc/check-tag-names': 0, 36 | 'jsdoc/require-returns-description': 0, 37 | }, 38 | overrides: [], 39 | }; 40 | 41 | module.exports = eslintConfig; 42 | -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | titleOnly: true 2 | types: 3 | - feat 4 | - fix 5 | - docs 6 | - dx 7 | - refactor 8 | - perf 9 | - test 10 | - workflow 11 | - build 12 | - ci 13 | - chore 14 | - types 15 | - wip 16 | - release 17 | - deps 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | 12 | jobs: 13 | Runner: 14 | timeout-minutes: 10 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ ubuntu-latest ] 20 | node-version: [ 16 ] 21 | steps: 22 | - name: Checkout Git Source 23 | uses: actions/checkout@v3 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | 30 | - name: Install dependencies 31 | run: | 32 | npm i npm@6 -g 33 | npm i 34 | 35 | - name: Continuous integration 36 | run: | 37 | npm run lint 38 | npm run test 39 | 40 | - name: Code coverage 41 | uses: codecov/codecov-action@v3.0.0 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/docs-build.yml: -------------------------------------------------------------------------------- 1 | name: Docs Build 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | branches: 8 | - master 9 | paths: 10 | - '**' 11 | 12 | jobs: 13 | docs-build: 14 | timeout-minutes: 10 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Git Source 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '16' 25 | 26 | - name: Install dependencies 27 | run: npm i lodash@4 -D 28 | 29 | - name: Build docs 30 | run: npm run docs:build 31 | 32 | - name: Deploy to GitHub Pages 33 | if: success() 34 | uses: peaceiris/actions-gh-pages@v3 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_dir: ./docs_dist 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | docs_dist 5 | reports 6 | *.sw* 7 | *.un~ 8 | .idea 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macaca WD Client 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Package quality][quality-image]][quality-url] 5 | [![build status][CI-image]][CI-url] 6 | [![Test coverage][codecov-image]][codecov-url] 7 | [![node version][node-image]][node-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/macaca-wd.svg 10 | [npm-url]: https://npmjs.org/package/macaca-wd 11 | [quality-image]: https://packagequality.com/shield/macaca-wd.svg 12 | [quality-url]: https://packagequality.com/#?package=macaca-wd 13 | [CI-image]: https://github.com/macacajs/macaca-wd/actions/workflows/ci.yml/badge.svg 14 | [CI-url]: https://github.com/macacajs/macaca-wd/actions/workflows/ci.yml 15 | [codecov-image]: https://img.shields.io/codecov/c/github/macacajs/macaca-wd.svg?logo=codecov 16 | [codecov-url]: https://app.codecov.io/gh/macacajs/macaca-wd 17 | [node-image]: https://img.shields.io/badge/node.js-%3E=_8-green.svg 18 | [node-url]: http://nodejs.org/download/ 19 | 20 | Macaca WD Client is inspired by [admc/wd](//github.com/admc/wd), according to [W3C WebDriver](https://w3c.github.io/webdriver/). 21 | 22 | 23 | 24 | ## Contributors 25 | 26 | |[
xudafeng](https://github.com/xudafeng)
|[
ziczhu](https://github.com/ziczhu)
|[
zhangyuheng](https://github.com/zhangyuheng)
|[
paradite](https://github.com/paradite)
|[
meowtec](https://github.com/meowtec)
|[
yihuineng](https://github.com/yihuineng)
| 27 | | :---: | :---: | :---: | :---: | :---: | :---: | 28 | |[
Jodeee](https://github.com/Jodeee)
|[
snapre](https://github.com/snapre)
|[
zivyangll](https://github.com/zivyangll)
|[
tsj1107](https://github.com/tsj1107)
|[
megolee](https://github.com/megolee)
|[
kobe990](https://github.com/kobe990)
| 29 | [
centy720](https://github.com/centy720)
|[
zhuyali](https://github.com/zhuyali)
30 | 31 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Thu Oct 12 2023 11:41:18 GMT+0800`. 32 | 33 | 34 | 35 | ## Installation 36 | 37 | ```bash 38 | $ npm i macaca-wd --save-dev 39 | ``` 40 | 41 | ## Documentation 42 | 43 | https://macacajs.github.io/macaca-wd 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const macacaEcosystem = require('macaca-ecosystem'); 6 | const traceFragment = require('macaca-ecosystem/lib/trace-fragment'); 7 | 8 | const { name } = require('../../package'); 9 | 10 | function getAPIList() { 11 | const apisDir = path.resolve(__dirname, '..', 'apis'); 12 | return fs.readdirSync(apisDir) 13 | .map(item => path.resolve(apisDir, item)) 14 | .filter(item => fs.statSync(item).isDirectory()) 15 | .map(groupDir => { 16 | const groupName = path.relative(apisDir, groupDir); 17 | const children = fs.readdirSync(groupDir).map(item => { 18 | const fileName = item.replace(path.extname(item), ''); 19 | return [ 20 | `${groupName}/${fileName}`, 21 | fileName, 22 | ]; 23 | }); 24 | return { 25 | title: groupName, 26 | collapsable: false, 27 | children, 28 | }; 29 | }); 30 | } 31 | 32 | module.exports = { 33 | dest: 'docs_dist', 34 | base: `/${name}/`, 35 | locales: { 36 | '/': { 37 | lang: 'en-US', 38 | title: 'Macaca WD', 39 | description: 'Node.js WebDriver Client for Macaca', 40 | }, 41 | '/zh/': { 42 | lang: 'zh-CN', 43 | title: 'Macaca WD', 44 | description: 'Macaca Node.js 客户端 API 模块', 45 | }, 46 | }, 47 | head: [ 48 | ['link', { 49 | rel: 'icon', 50 | href: 'https://macacajs.github.io/assets/favicon.ico' 51 | }], 52 | ['script', { 53 | async: true, 54 | src: 'https://www.googletagmanager.com/gtag/js?id=UA-49226133-2', 55 | }, ''], 56 | ['script', {}, ` 57 | window.dataLayer = window.dataLayer || []; 58 | function gtag(){dataLayer.push(arguments);} 59 | gtag('js', new Date()); 60 | gtag('config', 'UA-49226133-2'); 61 | `], 62 | ['script', {}, traceFragment], 63 | ], 64 | serviceWorker: true, 65 | themeConfig: { 66 | repo: `macacajs/${name}`, 67 | editLinks: true, 68 | docsDir: 'docs', 69 | locales: { 70 | '/': { 71 | label: 'English', 72 | selectText: 'Languages', 73 | editLinkText: 'Edit this page on GitHub', 74 | lastUpdated: 'Last Updated', 75 | serviceWorker: { 76 | updatePopup: { 77 | message: 'New content is available.', 78 | buttonText: 'Refresh', 79 | }, 80 | }, 81 | nav: [ 82 | macacaEcosystem.en, 83 | ], 84 | sidebar: { 85 | '/apis/': getAPIList(), 86 | }, 87 | }, 88 | '/zh/': { 89 | label: '简体中文', 90 | selectText: '选择语言', 91 | editLinkText: '在 GitHub 上编辑此页', 92 | lastUpdated: '上次更新', 93 | serviceWorker: { 94 | updatePopup: { 95 | message: '发现新内容可用', 96 | buttonText: '刷新', 97 | }, 98 | }, 99 | nav: [ 100 | macacaEcosystem.zh, 101 | ], 102 | sidebar: { 103 | '/zh/apis/': getAPIList(), 104 | }, 105 | }, 106 | }, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /docs/.vuepress/public/logo/macaca.svg: -------------------------------------------------------------------------------- 1 | 2 | Macaca 3 | Created with Sketch. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | $textColor = #2c3e50 2 | $borderColor = #eaecef 3 | $accentColor = #ee6723 4 | $codeBgColor = #282c34 -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo/macaca.svg 4 | actionText: API Documents 5 | actionLink: /apis/ 6 | --- 7 | 8 | ::: tip Introduction 9 | Macaca is an open-source automation test solution for native, hybrid, mobile web and web application on mobile and desktop platforms. 10 | ::: 11 | 12 | ## Installation 13 | 14 | ```bash 15 | $ npm i macaca-wd -D 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```javascript 21 | const wd = require('macaca-wd'); 22 | 23 | const remoteConfig = { 24 | host: 'localhost', 25 | port: 3456 26 | }; 27 | 28 | var driver = wd.promiseChainRemote(remoteConfig); 29 | 30 | before(function() { 31 | return driver.init({ 32 | platformName: 'desktop', // iOS, Android, Desktop, Playwright 33 | browserName: 'chrome', // chromium, firefox, webkit 34 | app: 'path/to/app', // Only for mobile 35 | }); 36 | }); 37 | 38 | after(function() { 39 | return driver 40 | .sleep(1000) 41 | .quit(); 42 | }); 43 | 44 | it('#1 should', function() { 45 | 46 | ... 47 | 48 | }); 49 | 50 | ... 51 | 52 | ``` 53 | 54 | ## Mixin Helper 55 | 56 | ```javascript 57 | import wd from 'macaca-wd'; 58 | import { 59 | extendsMixIn, 60 | } from 'macaca-wd/lib/helper' 61 | 62 | extendsMixIn(wd) 63 | ``` 64 | 65 | ## Extend WD chain 66 | 67 | ```javascript 68 | import wd from 'macaca-wd'; 69 | 70 | wd.addPromiseChainMethod(name, method); 71 | ``` 72 | 73 | [API](//macacajs.github.io/macaca-wd/apis) 74 | 75 | ## Demo 76 | 77 | - [demo-1](//github.com/app-bootstrap/web-app-bootstrap) 78 | - [demo-2](//github.com/macacajs/macaca-reporter) 79 | - [demo-3](//github.com/macaca-sample/antd-sample) 80 | - [demo-4](//github.com/xudafeng/autoresponsive-react) 81 | -------------------------------------------------------------------------------- /docs/apis/README.md: -------------------------------------------------------------------------------- 1 | # APIs 2 | 3 | ## Demo 4 | 5 | - [demo-1](//github.com/app-bootstrap/web-app-bootstrap) 6 | - [demo-2](//github.com/macacajs/macaca-reporter) 7 | - [demo-3](//github.com/macaca-sample/antd-sample) 8 | - [demo-4](//github.com/xudafeng/autoresponsive-react) 9 | -------------------------------------------------------------------------------- /docs/apis/alert/acceptAlert.md: -------------------------------------------------------------------------------- 1 | # acceptAlert 2 | 3 | * Accepts the currently displayed alert dialog. 4 | * @summary Support: Android iOS 5 | * @see {@link https://w3c.github.io/webdriver/#accept-alert|POST /session/:sessionId/accept_alert} 6 | -------------------------------------------------------------------------------- /docs/apis/alert/alertKeys.md: -------------------------------------------------------------------------------- 1 | # alertKeys 2 | 3 | * Sends keystrokes to a JavaScript prompt() dialog. 4 | * @summary Support: iOS 5 | * @see {@link https://w3c.github.io/webdriver/#send-alert-text|POST /session/:sessionId/alert_text} 6 | * @param keys 7 | -------------------------------------------------------------------------------- /docs/apis/alert/alertText.md: -------------------------------------------------------------------------------- 1 | # alertText 2 | 3 | * Gets the text of the currently displayed JavaScript alert(), confirm(), or prompt() dialog. 4 | * @summary Support: iOS 5 | * @see {@link https://w3c.github.io/webdriver/#send-alert-text|GET /session/:sessionId/alert_text} 6 | -------------------------------------------------------------------------------- /docs/apis/alert/dismissAlert.md: -------------------------------------------------------------------------------- 1 | # dismissAlert 2 | 3 | * Dismisses the currently displayed alert dialog. 4 | * @summary Support: Android iOS 5 | * @see {@link https://w3c.github.io/webdriver/#dismiss-alert|POST /session/:sessionId/dismiss_alert} 6 | 7 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElement.md: -------------------------------------------------------------------------------- 1 | # hasElement 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} using The locator strategy to use. 7 | * @param {string} value The search target. 8 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElementById.md: -------------------------------------------------------------------------------- 1 | # hasElementById 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The ID attribute. 7 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElementByName.md: -------------------------------------------------------------------------------- 1 | # hasElementByName 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The name attribute. 7 | 8 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElementByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # hasElementByPartialLinkText 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The partially text. 7 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElementByTagName.md: -------------------------------------------------------------------------------- 1 | # hasElementByTagName 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The tag name. 7 | -------------------------------------------------------------------------------- /docs/apis/assert/hasElementByXPath.md: -------------------------------------------------------------------------------- 1 | # hasElementByXPath 2 | 3 | * Check if element exists. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The XPath expression. 7 | -------------------------------------------------------------------------------- /docs/apis/browser/back.md: -------------------------------------------------------------------------------- 1 | # back 2 | 3 | * Navigate backwards in the browser history, if possible. 4 | * @summary Support: Android Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#back|POST /session/:sessionId/back} 6 | -------------------------------------------------------------------------------- /docs/apis/browser/click.md: -------------------------------------------------------------------------------- 1 | # click 2 | 3 | * Click on an element. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-element-click|POST /session/:sessionId/element/:id/click} 6 | -------------------------------------------------------------------------------- /docs/apis/browser/forward.md: -------------------------------------------------------------------------------- 1 | # forward 2 | 3 | * Navigate forwards in the browser history, if possible. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#back|POST /session/:sessionId/forward} 6 | -------------------------------------------------------------------------------- /docs/apis/browser/get.md: -------------------------------------------------------------------------------- 1 | # get 2 | 3 | * Navigate to a new URL. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get|POST /session/:sessionId/url} 6 | * @param url get a new url. 7 | -------------------------------------------------------------------------------- /docs/apis/browser/maximize.md: -------------------------------------------------------------------------------- 1 | # maximize 2 | 3 | * Maximize the specified window if not already maximized. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-maximize-window|POST /session/:sessionId/window/maximize} 6 | * @param handle window handle 7 | -------------------------------------------------------------------------------- /docs/apis/browser/refresh.md: -------------------------------------------------------------------------------- 1 | # refresh 2 | 3 | * Refresh the current page. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#refresh|POST /session/:sessionId/refresh} 6 | -------------------------------------------------------------------------------- /docs/apis/context/context.md: -------------------------------------------------------------------------------- 1 | # context 2 | 3 | * Set the current context. 4 | * @summary Support: Android iOS 5 | * @param {string} contextRef context reference from contexts 6 | * @returns {Promise} -------------------------------------------------------------------------------- /docs/apis/context/contexts.md: -------------------------------------------------------------------------------- 1 | # contexts 2 | 3 | * Get a list of the available contexts. 4 | * @summary Support: Android iOS 5 | * @returns A list of available contexts. -------------------------------------------------------------------------------- /docs/apis/context/currentContext.md: -------------------------------------------------------------------------------- 1 | # currentContext 2 | 3 | * Get the current context. 4 | * @summary Support: Android iOS -------------------------------------------------------------------------------- /docs/apis/cookie/allCookies.md: -------------------------------------------------------------------------------- 1 | # allCookies 2 | 3 | * Returns all cookies associated with the address of the current browsing context’s active document. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-all-cookies|GET /session/:sessionId/cookie} 6 | -------------------------------------------------------------------------------- /docs/apis/cookie/deleteAllCookies.md: -------------------------------------------------------------------------------- 1 | # deleteAllCookies 2 | 3 | * Allows deletion of all cookies associated with the active document’s address. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#delete-all-cookies|DELETE /session/:sessionId/cookie/:name} -------------------------------------------------------------------------------- /docs/apis/cookie/deleteCookie.md: -------------------------------------------------------------------------------- 1 | # deleteCookie 2 | 3 | * Delete either a single cookie by parameter name, or all the cookies associated with the active document’s address if name is undefined. 4 | * @summary Support: Web(WebView) 5 | * @param {string} name cookie name 6 | * @see {@link https://w3c.github.io/webdriver/#delete-cookie|DELETE /session/:sessionId/cookie/:name} 7 | -------------------------------------------------------------------------------- /docs/apis/cookie/setCookie.md: -------------------------------------------------------------------------------- 1 | # setCookie 2 | 3 | * Adds a single cookie to the cookie store associated with the active document’s address. 4 | * @summary Support: Web(WebView) 5 | * @param {object} cookie - example: {url: 'https://macacajs.github.io', name:'foo', value:'bar'} Optional cookie fields: secure, expiry 6 | * @see {@link https://w3c.github.io/webdriver/#add-cookie|POST /session/:sessionId/cookie} 7 | -------------------------------------------------------------------------------- /docs/apis/element/clear.md: -------------------------------------------------------------------------------- 1 | # clear 2 | 3 | * Clear a TEXTAREA or text INPUT element's value. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#element-clear|POST /session/:sessionId/element/:id/clear} 6 | -------------------------------------------------------------------------------- /docs/apis/element/element.md: -------------------------------------------------------------------------------- 1 | # element 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} using The locator strategy to use. 7 | * @param {string} value The search target. 8 | -------------------------------------------------------------------------------- /docs/apis/element/elementByCss.md: -------------------------------------------------------------------------------- 1 | # elementByCss 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The css selector 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByCssIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByCssIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The css selector 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByCssOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByCssOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The css selector 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementById.md: -------------------------------------------------------------------------------- 1 | # elementById 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The ID attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByIdIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByIdIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The ID attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByIdOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByIdOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The ID attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByName.md: -------------------------------------------------------------------------------- 1 | # elementByName 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The name attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByNameIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByNameIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The name attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByNameOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByNameOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The name attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkText 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The partially text 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByPartialLinkTextIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkTextIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The partially text 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByPartialLinkTextOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkTextOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The partially text 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByTagName.md: -------------------------------------------------------------------------------- 1 | # elementByTagName 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The tag name 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByTagNameIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByTagNameIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The tag name 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByTagNameOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByTagNameOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The tag name 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByXPath.md: -------------------------------------------------------------------------------- 1 | # elementByXPath 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The XPath expression 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByXPathIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByXPathIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The XPath expression 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementByXPathOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByXPathOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} value The XPath expression 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementIfExists.md: -------------------------------------------------------------------------------- 1 | # elementIfExists 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} using The locator strategy to use. 7 | * @param {string} value The search target. 8 | -------------------------------------------------------------------------------- /docs/apis/element/elementOrNull.md: -------------------------------------------------------------------------------- 1 | # elementOrNull 2 | 3 | * Search for an element on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/element} 6 | * @param {string} using The locator strategy to use. 7 | * @param {string} value The search target. 8 | -------------------------------------------------------------------------------- /docs/apis/element/elements.md: -------------------------------------------------------------------------------- 1 | # elements 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} using The locator strategy to use. 7 | * @param {string} value The search target. 8 | -------------------------------------------------------------------------------- /docs/apis/element/elementsByCss.md: -------------------------------------------------------------------------------- 1 | # elementsByCss 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The css selector 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementsById.md: -------------------------------------------------------------------------------- 1 | # elementsById 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The ID attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementsByName.md: -------------------------------------------------------------------------------- 1 | # elementsByName 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The name attribute 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementsByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # elementsByPartialLinkText 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The partially text 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementsByTagName.md: -------------------------------------------------------------------------------- 1 | # elementsByTagName 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The tag name 7 | -------------------------------------------------------------------------------- /docs/apis/element/elementsByXPath.md: -------------------------------------------------------------------------------- 1 | # elementsByXPath 2 | 3 | * Search for multiple elements on the page, starting from the document root. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#elements|POST /session/:sessionId/elements} 6 | * @param {string} value The XPath expression 7 | -------------------------------------------------------------------------------- /docs/apis/element/getComputedCss.md: -------------------------------------------------------------------------------- 1 | # getComputedCss 2 | 3 | * Query the value of an element's computed CSS property. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-element-css-value|GET /session/:sessionId/element/:id/css/:propertyName} 6 | * @param {string} propertyName The property name 7 | -------------------------------------------------------------------------------- /docs/apis/element/getProperty.md: -------------------------------------------------------------------------------- 1 | # getProperty 2 | 3 | * Get the result of a property of a element. 4 | * @summary Support: Android iOS Web(WebView). iOS: 'isVisible', 'isAccessible', 'isEnabled', 'type', 'label', 'name', 'value', Android: 'selected', 'description', 'text' 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-get-element-property|GET /session/:sessionId/element/:id/property/:name} 6 | * @param {string} name The property name 7 | -------------------------------------------------------------------------------- /docs/apis/element/getRect.md: -------------------------------------------------------------------------------- 1 | # getRect 2 | 3 | * Get the dimensions and coordinates of the given element with a object including x/y/height/width. 4 | * @summary Support: Android iOS. 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-get-element-rect|GET /session/:sessionId/element/:id/rect} 6 | -------------------------------------------------------------------------------- /docs/apis/element/isDisplayed.md: -------------------------------------------------------------------------------- 1 | # isDisplayed 2 | 3 | * Determine if an element is currently displayed. 4 | * @summary Support: Android Web(WebView) 5 | -------------------------------------------------------------------------------- /docs/apis/element/sendKeys.md: -------------------------------------------------------------------------------- 1 | # sendKeys 2 | 3 | * Send a sequence of key strokes to the active element. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @param {string} keys The keys sequence to be sent. 6 | * @see {@link https://w3c.github.io/webdriver/#dfn-element-send-keys|POST /session/:sessionId/element/:id/sendKeys} 7 | -------------------------------------------------------------------------------- /docs/apis/element/takeElementScreenshot.md: -------------------------------------------------------------------------------- 1 | # takeElementScreenshot 2 | 3 | * Element Screenshot. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#take-element-screenshot|GET /session/:sessionId/element/:id/screenshot} 6 | -------------------------------------------------------------------------------- /docs/apis/element/text.md: -------------------------------------------------------------------------------- 1 | # text 2 | 3 | * Returns the visible text for the element. 4 | * @summary Support: Android iOS Web(WebView) 5 | -------------------------------------------------------------------------------- /docs/apis/element/waitForElement.md: -------------------------------------------------------------------------------- 1 | # waitForElement 2 | 3 | * All the element-related methods above (except which suffixed with OrNull, IfExists) could be prefixed with the "waitFor-" (need to capitalize the 'e', e.g., waitForElementByCss) 4 | * @summary Support: Android iOS Web(WebView) 5 | * @param {string} using The locator strategy to use, omitted when using specific method like waitForElementByCss. 6 | * @param {string} value The css selector 7 | * @param {function} [asserter] The asserter function (commonly used asserter function can be found at wd.asserters) (optional) 8 | * @param {number} [timeout=1000ms] The timeout before find the element (optional) 9 | * @param {number} [interval=200ms] The interval between each searching (optional) 10 | * @example waitForElementByCss('btn', 2000, 100) Search for element which class name is 'btn' at intervals of 100ms, last for 2000ms. 11 | -------------------------------------------------------------------------------- /docs/apis/execute/execute.md: -------------------------------------------------------------------------------- 1 | # execute 2 | 3 | * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#executing-script|POST /session/:sessionId/execute} 6 | * @param code script 7 | * @param [args] script argument array -------------------------------------------------------------------------------- /docs/apis/next/fileChooser.md: -------------------------------------------------------------------------------- 1 | # fileChooser 2 | 3 | * Emitted when a file chooser is supposed to appear, such as after clicking the input[type="file"]. 4 | * @summary Support: Web(WebView) 5 | * @param {string} filePath 6 | -------------------------------------------------------------------------------- /docs/apis/next/frame.md: -------------------------------------------------------------------------------- 1 | # frame 2 | 3 | * Change focus to another frame on the page. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#switch-to-frame|POST /session/:sessionId/frame} 6 | * @param {string|number|null} frameRef Identifier(id/name) for the frame to change focus to 7 | -------------------------------------------------------------------------------- /docs/apis/next/keys.md: -------------------------------------------------------------------------------- 1 | # keys 2 | 3 | * Send a sequence of key strokes to the active window. 4 | * @summary Support: Android Web(WebView) More: https://github.com/alibaba/macaca/issues/487 5 | * @param {string} keys The keys sequence to be sent. 6 | -------------------------------------------------------------------------------- /docs/apis/next/sleep.md: -------------------------------------------------------------------------------- 1 | # sleep 2 | 3 | * Set the amount of time the driver should wait. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @param {number} ms The amount of time to wait, in milliseconds -------------------------------------------------------------------------------- /docs/apis/next/touch.md: -------------------------------------------------------------------------------- 1 | # touch 2 | 3 | * Apply touch actions on devices. 4 | * @summary Support: iOS, Android 5 | * @see {@link https://w3c.github.io/webdriver/#actions|POST /session/:sessionId/actions} 6 | * @param {string} action Name of the action, tap/doubleTap/press/pinch/rotate/drag. 7 | * @param [object] args Parameters of the action {@link https://github.com/alibaba/macaca/issues/366 more params} 8 | * @example driver.touch('doubleTap', {x: 100, y: 100}); 9 | -------------------------------------------------------------------------------- /docs/apis/screenshot/saveScreenshot.md: -------------------------------------------------------------------------------- 1 | # saveScreenshot 2 | 3 | * Save the screenshot of the current page. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @param {str} filepath The path to save the screenshot or left blank (will create a file in the system temp dir). 6 | * @returns The filepath of the screenshot. -------------------------------------------------------------------------------- /docs/apis/screenshot/takeScreenshot.md: -------------------------------------------------------------------------------- 1 | # takeScreenshot 2 | 3 | * Take a screenshot of the current page. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @returns The screenshot as a base64 encoded PNG. -------------------------------------------------------------------------------- /docs/apis/session/init.md: -------------------------------------------------------------------------------- 1 | # init 2 | 3 | * Create a new session. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-new-session|POST /session} 6 | * @param {Object} desired Desired Capabilities 7 | * @type session 8 | -------------------------------------------------------------------------------- /docs/apis/session/quit.md: -------------------------------------------------------------------------------- 1 | # quit 2 | 3 | * Delete the session. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-delete-session|DELETE /session/:sessionId} 6 | * @returns {Promise} 7 | * @type session -------------------------------------------------------------------------------- /docs/apis/session/sessions.md: -------------------------------------------------------------------------------- 1 | # sessions 2 | 3 | * Returns a list of the currently active sessions. 4 | * @summary Support: Android iOS Web(WebView) -------------------------------------------------------------------------------- /docs/apis/session/status.md: -------------------------------------------------------------------------------- 1 | # status 2 | 3 | * Query the server's current status. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @type session 6 | * @returns The server's current status. 7 | -------------------------------------------------------------------------------- /docs/apis/source/source.md: -------------------------------------------------------------------------------- 1 | # source 2 | 3 | * Get the current page source. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#dfn-get-page-source|GET /session/:sessionId/source} -------------------------------------------------------------------------------- /docs/apis/title/title.md: -------------------------------------------------------------------------------- 1 | # title 2 | 3 | * Get the current page title or focus activity or viewController. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-title|GET /session/:sessionId/title} -------------------------------------------------------------------------------- /docs/apis/url/url.md: -------------------------------------------------------------------------------- 1 | # url 2 | 3 | * Retrieve the URL of the current page. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-current-url|GET /session/:sessionId/url} 6 | -------------------------------------------------------------------------------- /docs/apis/window/close.md: -------------------------------------------------------------------------------- 1 | # close 2 | 3 | * Close the current window. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#close-window|DELETE /session/:sessionId/window} 6 | * @type window 7 | -------------------------------------------------------------------------------- /docs/apis/window/getWindowSize.md: -------------------------------------------------------------------------------- 1 | # getWindowSize 2 | 3 | * Get the size of the specified window. 4 | * @summary Support: Android iOS Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-window-size|GET /session/:sessionId/window/size} 6 | * @param [handle] window handle to set size for (optional, default: 'current') 7 | -------------------------------------------------------------------------------- /docs/apis/window/setWindowSize.md: -------------------------------------------------------------------------------- 1 | # setWindowSize 2 | 3 | * Change the size of the specified window. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#set-window-size|POST /session/:sessionId/window/size} 6 | * @param width width in pixels to set size to 7 | * @param height height in pixels to set size to 8 | * @param [handle] window handle to set size for (optional, default: 'current') 9 | -------------------------------------------------------------------------------- /docs/apis/window/window.md: -------------------------------------------------------------------------------- 1 | # window 2 | 3 | * Change focus to another window. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#switch-to-window|POST /session/:sessionId/window} 6 | -------------------------------------------------------------------------------- /docs/apis/window/windowHandle.md: -------------------------------------------------------------------------------- 1 | # windowHandle 2 | 3 | * Retrieve the current window handle. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-window-handle|GET /session/:sessionId/window_handle} 6 | -------------------------------------------------------------------------------- /docs/apis/window/windowHandles.md: -------------------------------------------------------------------------------- 1 | # windowHandles 2 | 3 | * Retrieve the list of all window handles available to the session. 4 | * @summary Support: Web(WebView) 5 | * @see {@link https://w3c.github.io/webdriver/#get-window-handles|GET /session/:sessionId/window_handles} 6 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo/macaca.svg 4 | actionText: API 文档 5 | actionLink: /zh/apis/ 6 | footer: MIT Licensed | Copyright © 2015-present Macaca 7 | --- 8 | 9 | ::: tip 开发体验 10 | API 文档 11 | ::: 12 | 13 | ## 安装 14 | 15 | ```bash 16 | $ npm i macaca-wd -D 17 | ``` 18 | 19 | ## 使用 20 | 21 | ```javascript 22 | const wd = require('macaca-wd'); 23 | 24 | const remoteConfig = { 25 | host: 'localhost', 26 | port: 3456 27 | }; 28 | 29 | var driver = wd.promiseChainRemote(remoteConfig); 30 | 31 | before(function() { 32 | return driver.init({ 33 | platformName: 'desktop', // iOS, Android, Desktop, Playwright 34 | browserName: 'chrome', // chromium, firefox, webkit 35 | app: 'path/to/app', // Only for mobile 36 | }); 37 | }); 38 | 39 | after(function() { 40 | return driver 41 | .sleep(1000) 42 | .quit(); 43 | }); 44 | 45 | it('#1 should', function() { 46 | 47 | ... 48 | 49 | }); 50 | 51 | ... 52 | 53 | ``` 54 | 55 | ## 集成 Helper 56 | 57 | ```javascript 58 | import wd from 'macaca-wd'; 59 | import { 60 | extendsMixIn, 61 | } from 'macaca-wd/lib/helper' 62 | 63 | extendsMixIn(wd) 64 | ``` 65 | 66 | ## 扩展更多 API 67 | 68 | ```javascript 69 | import wd from 'macaca-wd'; 70 | 71 | wd.addPromiseChainMethod(name, method); 72 | ``` 73 | 74 | [API](//macacajs.github.io/macaca-wd/zh/apis) 75 | 76 | ## 示例 77 | 78 | - [示例-1](//github.com/app-bootstrap/web-app-bootstrap) 79 | - [示例-2](//github.com/macacajs/macaca-reporter) 80 | - [示例-3](//github.com/macaca-sample/antd-sample) 81 | - [示例-4](//github.com/xudafeng/autoresponsive-react) 82 | -------------------------------------------------------------------------------- /docs/zh/apis/README.md: -------------------------------------------------------------------------------- 1 | # API 列表 2 | 3 | ## 示例 4 | 5 | - [示例-1](//github.com/app-bootstrap/web-app-bootstrap) 6 | - [示例-2](//github.com/macacajs/macaca-reporter) 7 | - [示例-3](//github.com/macaca-sample/antd-sample) 8 | - [示例-4](//github.com/xudafeng/autoresponsive-react) 9 | -------------------------------------------------------------------------------- /docs/zh/apis/alert/acceptAlert.md: -------------------------------------------------------------------------------- 1 | # acceptAlert 2 | 3 | * 功能描述: 接受当前弹出的 alert 提示框。 4 | * 支持平台: Android iOS 5 | * 标准链接: [POST /session/:sessionId/accept_alert](https://w3c.github.io/webdriver/#accept-alert) 6 | 7 | ## 示例 8 | 9 | ```javascript 10 | driver.acceptAlert(); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/alert/alertKeys.md: -------------------------------------------------------------------------------- 1 | # alertKeys 2 | 3 | * 功能描述: 发送命令至当前弹出的 alert 提示框。 4 | * 支持平台: iOS 5 | * 标准链接: [POST /session/:sessionId/alert_text](https://w3c.github.io/webdriver/#send-alert-text) 6 | * 参数: {string} keys 参数键 -------------------------------------------------------------------------------- /docs/zh/apis/alert/alertText.md: -------------------------------------------------------------------------------- 1 | # alertText 2 | 3 | * 功能描述: 获取当前弹出的 alert 提示框的文本。 4 | * 支持平台: iOS 5 | * 标准链接: [GET /session/:sessionId/alert_text](https://w3c.github.io/webdriver/#send-alert-text) -------------------------------------------------------------------------------- /docs/zh/apis/alert/dismissAlert.md: -------------------------------------------------------------------------------- 1 | # dismissAlert 2 | 3 | * 功能描述: 关闭当前弹出的 alert 提示框。 4 | * 支持平台: Android iOS 5 | * 标准链接: [POST /session/:sessionId/dismiss_alert](https://w3c.github.io/webdriver/#dismiss-alert) -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElement.md: -------------------------------------------------------------------------------- 1 | # hasElement 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} using 元素定位器策略。 7 | * 参数: {string} value 对应的值。 8 | ## 示例 9 | 10 | ```javascript 11 | driver.hasElement('id', 'com.demo.demo:id/searchEdit'); 12 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElementById.md: -------------------------------------------------------------------------------- 1 | # hasElementById 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value ID 属性。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.hasElementById('com.demo.demo:id/searchEdit'); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElementByName.md: -------------------------------------------------------------------------------- 1 | # hasElementByName 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value name 属性。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.hasElementByName('搜索'); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElementByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # hasElementByPartialLinkText 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 部分文本内容。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.hasElementByPartialLinkText('搜索'); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElementByTagName.md: -------------------------------------------------------------------------------- 1 | # hasElementByTagName 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 标签名称。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.hasElementByTagName('搜索'); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/assert/hasElementByXPath.md: -------------------------------------------------------------------------------- 1 | # hasElementByXPath 2 | 3 | * 功能描述: 检查元素是否存在。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value XPath 表达式。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.hasElementByXPath('//*[@resource-id="com.demo.demo:id/searchEdit"]'); 11 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/browser/back.md: -------------------------------------------------------------------------------- 1 | # back 2 | 3 | * 功能描述: 在浏览器历史记录中向后导航。 4 | * 支持平台: Android Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/back](https://w3c.github.io/webdriver/#back) -------------------------------------------------------------------------------- /docs/zh/apis/browser/click.md: -------------------------------------------------------------------------------- 1 | # click 2 | 3 | * 功能描述: 单击元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element/:id/click](https://w3c.github.io/webdriver/#dfn-element-click) -------------------------------------------------------------------------------- /docs/zh/apis/browser/forward.md: -------------------------------------------------------------------------------- 1 | # forward 2 | 3 | * 功能描述: 在浏览器历史记录中向前导航。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/forward](https://w3c.github.io/webdriver/#forward) 6 | -------------------------------------------------------------------------------- /docs/zh/apis/browser/get.md: -------------------------------------------------------------------------------- 1 | # get 2 | 3 | * 功能描述: 打开一个新的 url 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/url](https://w3c.github.io/webdriver/#get) 6 | * 参数: {string} url 链接地址 -------------------------------------------------------------------------------- /docs/zh/apis/browser/maximize.md: -------------------------------------------------------------------------------- 1 | # maximize 2 | 3 | * 功能描述: 最大化窗口 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/window/maximize](https://w3c.github.io/webdriver/#dfn-maximize-window) 6 | * 参数: {string} handle 窗口句柄 -------------------------------------------------------------------------------- /docs/zh/apis/browser/refresh.md: -------------------------------------------------------------------------------- 1 | # refresh 2 | 3 | * 功能描述: 刷新当前页面 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/refresh](https://w3c.github.io/webdriver/#refresh) -------------------------------------------------------------------------------- /docs/zh/apis/context/context.md: -------------------------------------------------------------------------------- 1 | # context 2 | 3 | * 功能描述: 设置当前上下文 4 | * 支持平台: Android iOS 5 | * 参数: {string} contextRef 上下文引用 6 | * 返回: {Promise} -------------------------------------------------------------------------------- /docs/zh/apis/context/contexts.md: -------------------------------------------------------------------------------- 1 | # contexts 2 | 3 | * 功能描述: 获取可用的上下文列表 4 | * 支持平台: Android iOS 5 | * 返回: 可用上下文列表 -------------------------------------------------------------------------------- /docs/zh/apis/context/currentContext.md: -------------------------------------------------------------------------------- 1 | # currentContext 2 | 3 | * 功能描述: 获取当前上下文列表 4 | * 支持平台: Android iOS -------------------------------------------------------------------------------- /docs/zh/apis/cookie/allCookies.md: -------------------------------------------------------------------------------- 1 | # allCookies 2 | 3 | * 功能描述: 获取与当前上下文关联的所有 cookie。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/cookie](https://w3c.github.io/webdriver/#get-all-cookies) -------------------------------------------------------------------------------- /docs/zh/apis/cookie/deleteAllCookies.md: -------------------------------------------------------------------------------- 1 | # deleteAllCookies 2 | 3 | * 功能描述: 删除当前上下文关联的所有 cookie。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [DELETE /session/:sessionId/cookie/:name](https://w3c.github.io/webdriver/#delete-all-cookies) -------------------------------------------------------------------------------- /docs/zh/apis/cookie/deleteCookie.md: -------------------------------------------------------------------------------- 1 | # deleteCookie 2 | 3 | * 功能描述: 删除指定 cookie,如果名称未定义,则删除当前上下文关联的所有 cookie。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [DELETE /session/:sessionId/cookie/:name](https://w3c.github.io/webdriver/#delete-cookie) 6 | * 参数: {string} name cookie 名称 -------------------------------------------------------------------------------- /docs/zh/apis/cookie/setCookie.md: -------------------------------------------------------------------------------- 1 | # setCookie 2 | 3 | * 功能描述: 将单个 cookie 添加到与活动文档地址关联的 cookie 存储中。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/cookie](https://w3c.github.io/webdriver/#add-cookie) 6 | * 参数: {object} cookie 对象。 7 | ## 示例 8 | 9 | ```javascript 10 | driver.setCookie({ 11 | url: 'https://macacajs.github.io', 12 | name: 'foo', 13 | value: 'bar', 14 | 15 | }) 16 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/element/clear.md: -------------------------------------------------------------------------------- 1 | # clear 2 | 3 | * 功能描述: 清除 TEXTAREA 或 INPUT 元素的值。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element/:id/clear](https://w3c.github.io/webdriver/#element-clear) -------------------------------------------------------------------------------- /docs/zh/apis/element/element.md: -------------------------------------------------------------------------------- 1 | # element 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} using 元素定位器策略。 7 | * 参数: {string} value 对应的值。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByCss.md: -------------------------------------------------------------------------------- 1 | # elementByCss 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value css 选择器。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByCssIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByCssIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value css 选择器。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByCssOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByCssOrNull 2 | 3 | * 功能描述: 搜索页面上的元素 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value css 选择器。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementById.md: -------------------------------------------------------------------------------- 1 | # elementById 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value ID 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByIdIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByIdIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value ID 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByIdOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByIdOrNull 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value ID 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByName.md: -------------------------------------------------------------------------------- 1 | # elementByName 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value name 属性。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByNameIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByNameIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value name 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByNameOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByNameOrNull 2 | 3 | * 功能描述: 搜索页面上的元素 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value name 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkText 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 部分文本内容。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByPartialLinkTextIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkTextIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 部分文本内容。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByPartialLinkTextOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByPartialLinkTextOrNull 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 部分文本内容。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByTagName.md: -------------------------------------------------------------------------------- 1 | # elementByTagName 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 标签名称。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByTagNameIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByTagNameIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 标签名称。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByTagNameOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByTagNameOrNull 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 标签名称。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByXPath.md: -------------------------------------------------------------------------------- 1 | # elementByXPath 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value XPath 表达式。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByXPathIfExists.md: -------------------------------------------------------------------------------- 1 | # elementByXPathIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value XPath 表达式。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementByXPathOrNull.md: -------------------------------------------------------------------------------- 1 | # elementByXPathOrNull 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value XPath 表达式。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementIfExists.md: -------------------------------------------------------------------------------- 1 | # elementIfExists 2 | 3 | * 功能描述: 搜索页面上的元素,如果存在则返回对应元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} using 元素定位器策略。 7 | * 参数: {string} value 对应的值。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementOrNull.md: -------------------------------------------------------------------------------- 1 | # elementOrNull 2 | 3 | * 功能描述: 搜索页面上的元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} using 元素定位器策略。 7 | * 参数: {string} value 对应的值。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elements.md: -------------------------------------------------------------------------------- 1 | # elements 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/elements](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} using 元素定位器策略。 7 | * 参数: {string} value 对应的值。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsByCss.md: -------------------------------------------------------------------------------- 1 | # elementsByCss 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value css 选择器。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsById.md: -------------------------------------------------------------------------------- 1 | # elementsById 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value ID 属性。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsByName.md: -------------------------------------------------------------------------------- 1 | # elementsByName 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value name 属性。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsByPartialLinkText.md: -------------------------------------------------------------------------------- 1 | # elementsByPartialLinkText 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 部分文本内容。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsByTagName.md: -------------------------------------------------------------------------------- 1 | # elementsByTagName 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value 标签名称。 -------------------------------------------------------------------------------- /docs/zh/apis/element/elementsByXPath.md: -------------------------------------------------------------------------------- 1 | # elementsByXPath 2 | 3 | * 功能描述: 搜索页面上的多个元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element](https://w3c.github.io/webdriver/#elements) 6 | * 参数: {string} value XPath 表达式。 -------------------------------------------------------------------------------- /docs/zh/apis/element/getComputedCss.md: -------------------------------------------------------------------------------- 1 | # getComputedCss 2 | 3 | * 功能描述: 获取元素 CSS 的值 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/element/:id/css/:propertyName](https://w3c.github.io/webdriver/#get-element-css-value) 6 | * 参数: {string} propertyName 属性名称 7 | -------------------------------------------------------------------------------- /docs/zh/apis/element/getProperty.md: -------------------------------------------------------------------------------- 1 | # getProperty 2 | 3 | * 功能描述: 获取元素的属性 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element/:id/property/:name](https://w3c.github.io/webdriver/#dfn-get-element-property) 6 | * 参数: {object} name 属性名 -------------------------------------------------------------------------------- /docs/zh/apis/element/getRect.md: -------------------------------------------------------------------------------- 1 | # getRect 2 | 3 | * 功能描述: 获取指定元素的尺寸和坐标。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/element/:id/rect](https://w3c.github.io/webdriver/#dfn-get-element-rect) -------------------------------------------------------------------------------- /docs/zh/apis/element/isDisplayed.md: -------------------------------------------------------------------------------- 1 | # isDisplayed 2 | 3 | * 功能描述: 判断当前元素是否可见 4 | * 支持平台: Android Web(WebView) -------------------------------------------------------------------------------- /docs/zh/apis/element/sendKeys.md: -------------------------------------------------------------------------------- 1 | # sendKeys 2 | 3 | * 功能描述: 将一系列按键发送给活动元素。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element/:id/sendKeys](https://w3c.github.io/webdriver/#dfn-element-send-keys) 6 | * 参数: {string} keys 要发送的键序列。 -------------------------------------------------------------------------------- /docs/zh/apis/element/takeElementScreenshot.md: -------------------------------------------------------------------------------- 1 | # takeElementScreenshot 2 | 3 | * 功能描述: 元素截图。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/element/:id/screenshot](https://w3c.github.io/webdriver/#take-element-screenshot) -------------------------------------------------------------------------------- /docs/zh/apis/element/text.md: -------------------------------------------------------------------------------- 1 | # text 2 | 3 | * 功能描述: 返回元素的可见文本。 4 | * 支持平台: Android iOS Web(WebView) -------------------------------------------------------------------------------- /docs/zh/apis/element/waitForElement.md: -------------------------------------------------------------------------------- 1 | # waitForElement 2 | 3 | * 功能描述: 上述所有与元素相关的方法(后缀为 OrNull、IfExists 的方法除外)都可以使用“waitFor-”作为前缀(需要将“e”大写,例如 waitForElementByCss)动态等待元素加载。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 参数: {string} using 元素定位器策略,在使用 waitForElementByCss 等特定方法时省略。 6 | * 参数: {string} value 对应的值。 7 | * 参数: {function} 断言器函数(常用的断言器函数可以在wd.asserters中找到)(可选)。 8 | * 参数: {number} timeout 查找元素前的超时时间,单位:ms(可选) 9 | * 参数: {number} interval 每次搜索的间隔时间,单位:ms(可选) 10 | 11 | ## 示例 12 | 13 | ```javascript 14 | // 以 100ms 的间隔搜索类名为 'btn' 的元素,持续 2000ms。 15 | driver.waitForElementByCss('btn', 2000, 100) 16 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/execute/execute.md: -------------------------------------------------------------------------------- 1 | # execute 2 | 3 | * 功能描述: 将一段 JavaScript 注入页面,以便在当前上下文中执行。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST //session/:sessionId/execute](https://w3c.github.io/webdriver/#executing-script) 6 | * 参数: {string} code 代码脚本。 7 | * 参数: [args] 脚本参数数组 -------------------------------------------------------------------------------- /docs/zh/apis/next/fileChooser.md: -------------------------------------------------------------------------------- 1 | # fileChooser 2 | 3 | * 功能描述: 通过文件选择器上传文件。 4 | * 支持平台: Web(WebView) 5 | * 参数: {string} filePath 文件路径 6 | 7 | ## 示例 8 | 9 | ```javascript 10 | .sleep(1000) 11 | .then(() => { 12 | driver.fileChooser('./image/test.png'); 13 | driver.clickOn('图片'); 14 | }) 15 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/next/frame.md: -------------------------------------------------------------------------------- 1 | # frame 2 | 3 | * 功能描述: 将焦点切换到页面上的另一个框架。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/frame](https://w3c.github.io/webdriver/#switch-to-frame) 6 | * 参数: {string|number|null} frameRef 将焦点更改为的框架的标识符(id/name) -------------------------------------------------------------------------------- /docs/zh/apis/next/keys.md: -------------------------------------------------------------------------------- 1 | # keys 2 | 3 | * 功能描述: 将一系列击键发送到活动窗口。 4 | * 支持平台: Android Web(WebView) 更多: https://github.com/alibaba/macaca/issues/487 5 | * 参数: {string} keys 要发送的键序列 -------------------------------------------------------------------------------- /docs/zh/apis/next/sleep.md: -------------------------------------------------------------------------------- 1 | # sleep 2 | 3 | * 功能描述: 设置程序应等待的时间。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 参数: {string} ms 等待的时间,以毫秒为单位 -------------------------------------------------------------------------------- /docs/zh/apis/next/touch.md: -------------------------------------------------------------------------------- 1 | # touch 2 | 3 | * 功能描述: 在设备上进行触摸操作。 4 | * 支持平台: iOS Android 5 | * 标准链接: [POST /session/:sessionId/actions](https://w3c.github.io/webdriver/#actions) 6 | * 参数: {string} action 动作名称, tap/doubleTap/press/pinch/rotate/drag 7 | * 参数: {object} args 动作参数, [更多参数](https://github.com/alibaba/macaca/issues/366) 8 | 9 | ## 示例 10 | 11 | ```javascript 12 | driver.touch('doubleTap', { x: 100, y: 100 }); 13 | ``` -------------------------------------------------------------------------------- /docs/zh/apis/screenshot/saveScreenshot.md: -------------------------------------------------------------------------------- 1 | # saveScreenshot 2 | 3 | * 功能描述: 保存当前页面的截图。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 参数: {string} filepath 保存屏幕截图的路径或为空(将在系统临时目录中创建一个文件)。 6 | * 返回: {string} 屏幕截图的文件路径。 7 | -------------------------------------------------------------------------------- /docs/zh/apis/screenshot/takeScreenshot.md: -------------------------------------------------------------------------------- 1 | # takeScreenshot 2 | 3 | * 功能描述: 截取当前页面的屏幕截图。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 返回: 截图为 base64 编码的 PNG。 6 | -------------------------------------------------------------------------------- /docs/zh/apis/session/init.md: -------------------------------------------------------------------------------- 1 | # init 2 | 3 | * 功能描述: 创建一个新会话。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [POST /session](https://w3c.github.io/webdriver/#dfn-new-session) 6 | * 参数: {Object} desired 参数 7 | * 类型: session -------------------------------------------------------------------------------- /docs/zh/apis/session/quit.md: -------------------------------------------------------------------------------- 1 | # quit 2 | 3 | * 功能描述: 删除会话。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [DELETE /session/:sessionId](https://w3c.github.io/webdriver/#dfn-delete-session) 6 | * 返回: {Promise} 7 | * 类型: session -------------------------------------------------------------------------------- /docs/zh/apis/session/sessions.md: -------------------------------------------------------------------------------- 1 | # sessions 2 | 3 | * 功能描述: 返回当前活动会话的列表。 4 | * 支持平台: Android iOS Web(WebView) -------------------------------------------------------------------------------- /docs/zh/apis/session/status.md: -------------------------------------------------------------------------------- 1 | # status 2 | 3 | * 功能描述: 查询服务器的当前状态。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 类型: session 6 | * 返回:服务器的当前状态。 -------------------------------------------------------------------------------- /docs/zh/apis/source/source.md: -------------------------------------------------------------------------------- 1 | # source 2 | 3 | * 功能描述: 获取当前页面 DOM 序列化字符串。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/source](https://w3c.github.io/webdriver/#dfn-get-page-source) -------------------------------------------------------------------------------- /docs/zh/apis/title/title.md: -------------------------------------------------------------------------------- 1 | # title 2 | 3 | * 功能描述: 获取当前上下文的文档标题 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/title](https://w3c.github.io/webdriver/#get-title) -------------------------------------------------------------------------------- /docs/zh/apis/url/url.md: -------------------------------------------------------------------------------- 1 | # url 2 | 3 | * 功能描述: 获取当前页面 URL 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/url](https://w3c.github.io/webdriver/#get-current-url) -------------------------------------------------------------------------------- /docs/zh/apis/window/close.md: -------------------------------------------------------------------------------- 1 | # close 2 | 3 | * 功能描述: 关闭当前窗口。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [DELETE /session/:sessionId/window](https://w3c.github.io/webdriver/#close-window) 6 | * 类型: window -------------------------------------------------------------------------------- /docs/zh/apis/window/getWindowSize.md: -------------------------------------------------------------------------------- 1 | # getWindowSize 2 | 3 | * 功能描述: 获取指定窗口的大小。 4 | * 支持平台: Android iOS Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/window/size](https://w3c.github.io/webdriver/#get-window-size) 6 | * 参数: {string} handle 获取大小的窗口句柄(可选,默认值:'current') -------------------------------------------------------------------------------- /docs/zh/apis/window/setWindowSize.md: -------------------------------------------------------------------------------- 1 | # setWindowSize 2 | 3 | * 功能描述: 更改指定窗口的大小。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/window/size](https://w3c.github.io/webdriver/#set-window-size) 6 | * 参数: {number} width 以像素为单位设置尺寸的宽度 7 | * 参数: {number} height 以像素为单位设置尺寸的高度 8 | * 参数: {string} handle 设置大小的窗口句柄(可选,默认值:'current') -------------------------------------------------------------------------------- /docs/zh/apis/window/window.md: -------------------------------------------------------------------------------- 1 | # window 2 | 3 | * 功能描述: 将焦点切换到另一个窗口。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [POST /session/:sessionId/window](https://w3c.github.io/webdriver/#switch-to-window) -------------------------------------------------------------------------------- /docs/zh/apis/window/windowHandle.md: -------------------------------------------------------------------------------- 1 | # windowHandle 2 | 3 | * 功能描述: 获取当前窗口句柄。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/window_handle](https://w3c.github.io/webdriver/#get-window-handle) -------------------------------------------------------------------------------- /docs/zh/apis/window/windowHandles.md: -------------------------------------------------------------------------------- 1 | # windowHandles 2 | 3 | * 功能描述: 检索会话可用的所有窗口句柄列表。 4 | * 支持平台: Web(WebView) 5 | * 标准链接: [GET /session/:sessionId/window_handles](https://w3c.github.io/webdriver/#get-window-handles) -------------------------------------------------------------------------------- /lib/helper/assert.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const shouldContains = (...substrs) => str => { 6 | substrs.forEach(substr => assert.include(str, substr)); 7 | }; 8 | 9 | module.exports = wd => { 10 | /** 11 | * Check if element has pointed text. 12 | * @function hasText 13 | * @summary Support: Web(WebView) 14 | * @param {string} text content. 15 | * @type assert 16 | * @return {Promise.} 17 | */ 18 | wd.addElementPromiseChainMethod('hasText', function(...texts) { 19 | return this.text().then(shouldContains(...texts)); 20 | }); 21 | 22 | /** 23 | * Check if element is existed. 24 | * @function hasElementByCss 25 | * @summary Support: Web(WebView) 26 | * @param {string} text content. 27 | * @type assert 28 | * @return {Promise.} 29 | */ 30 | wd.addPromiseChainMethod('hasElementByCss', function(cssSelector) { 31 | return this.elementByCssIfExists(cssSelector).then(d => { 32 | if (!d) { 33 | throw new Error(`Element ${cssSelector} should be existed.`); 34 | } 35 | }); 36 | }); 37 | 38 | /** 39 | * Check if title exists. 40 | * @function assertTitle 41 | * @summary Support: Web(WebView) 42 | * @param {string} title - title of the web page. 43 | * @type assert 44 | * @return {Promise.} 45 | */ 46 | wd.addPromiseChainMethod('assertTitle', function(title) { 47 | return this.title().then(realTitle => { 48 | assert( 49 | title === realTitle, 50 | `expect title to be '${title}' but got ${realTitle}` 51 | ); 52 | }); 53 | }); 54 | 55 | /** 56 | * Check if element's attribute right. 57 | * @function assertAttribute 58 | * @summary Support: Web(WebView) 59 | * @param {string} attribute - attribute's name. 60 | * @param {string} value - expected value. 61 | * @type assert 62 | * @return {Promise.} 63 | */ 64 | wd.addPromiseChainMethod('assertAttribute', function(attribute, value) { 65 | return this.execute( 66 | `return window.__macaca_current_element.getAttribute('${attribute}')` 67 | ).then(realValue => { 68 | assert( 69 | value === realValue, 70 | `expect attribute ${attribute} to be '${value}' but got '${realValue}'` 71 | ); 72 | }); 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /lib/helper/coverage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const _ = require('xutil'); 6 | const program = require('commander'); 7 | 8 | program 9 | .allowUnknownOption() 10 | .option('--coverage-ignore [coverageIgnore]', 'ignore RegExp for coverage ') 11 | .parse(process.argv); 12 | 13 | const xlogger = require('xlogger'); 14 | 15 | const logger = xlogger.Logger({ 16 | closeFile: true, 17 | }); 18 | 19 | const cwd = process.cwd(); 20 | 21 | module.exports = wd => { 22 | /** 23 | * Generate the coverage reporter. 24 | * @function coverage 25 | * @summary Support: Web(WebView) 26 | * @type utility 27 | * @return {Promise.} 28 | */ 29 | wd.addPromiseChainMethod('coverage', function(opts = {}) { 30 | let { 31 | coverageIgnore = program.coverageIgnore, 32 | keys = [], 33 | coverageHandler, 34 | } = opts; 35 | const tempDir = path.join(cwd, 'coverage', '.temp'); 36 | _.mkdir(tempDir); 37 | return this.execute('return { allKeys: Object.keys(window.__coverage__) }') 38 | .then(async res => { 39 | const { allKeys } = res; 40 | if (!keys.length && allKeys) { 41 | keys = allKeys; 42 | } 43 | if (coverageIgnore) { 44 | logger.info('handle coverageIgnore'); 45 | const ignoreReg = new RegExp(coverageIgnore, 'i'); 46 | keys = keys.filter(k => { 47 | return !ignoreReg.test(k); 48 | }); 49 | } 50 | if (!keys.length) { 51 | logger.info('coverage keys is empty'); 52 | return; 53 | } 54 | const covFile = path.join(tempDir, `${+new Date()}_coverage.json`); 55 | const writer = fs.createWriteStream(covFile); 56 | writer.write('{'); 57 | for (let i = 0; i < keys.length; i++) { 58 | const coverage = await this.execute(`return window.__coverage__['${keys[i]}']`); 59 | if (coverage) { 60 | const coverageJSON = JSON.stringify(coverage); 61 | let covChunk; 62 | if (coverageHandler) { 63 | covChunk = await coverageHandler(keys[i], coverageJSON); 64 | } 65 | writer.write(covChunk || `"${keys[i]}":${coverageJSON}`); 66 | if (i < keys.length - 1) { 67 | writer.write(','); 68 | } 69 | } 70 | } 71 | writer.write('}'); 72 | await new Promise(resolve => { 73 | writer.end(() => { 74 | writer.close(); 75 | resolve(); 76 | }); 77 | }); 78 | }); 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /lib/helper/element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const _ = require('xutil'); 6 | const Promise = require('bluebird'); 7 | const { uuid, mkdir } = _; 8 | const { appendToContext } = require('macaca-reporter'); 9 | const domEventLib = require.resolve('dom-event-simulate'); 10 | const domEventLibSource = fs.readFileSync(domEventLib, 'utf8'); 11 | 12 | const cwd = process.cwd(); 13 | const reporterDir = process.env.MACACA_REPORTER_DIR || cwd; 14 | const reportsPath = path.join(reporterDir, 'reports'); 15 | 16 | const safeQuote = string => JSON.stringify(String(string)); 17 | 18 | module.exports = wd => { 19 | /** 20 | * Initial browser window. 21 | * @function initWindow 22 | * @summary Support: Web(WebView) 23 | * @param {object} browser options. 24 | * @type utility 25 | * @return {Promise.} 26 | */ 27 | wd.addPromiseChainMethod('initWindow', function(options = {}) { 28 | return this.init({ 29 | platformName: 'playwright', 30 | browserName: 'chromium', 31 | deviceScaleFactor: 2, 32 | viewport: { 33 | width: options.width, 34 | height: options.height, 35 | }, 36 | ...options, 37 | }); 38 | }); 39 | 40 | /** 41 | * Get webpage from url util the page ready. 42 | * @function getUrl 43 | * @summary Support: Web(WebView) 44 | * @param {string} the pointed url. 45 | * @type utility 46 | * @return {Promise.} 47 | */ 48 | wd.addPromiseChainMethod('getUrl', function(url) { 49 | return this 50 | .get(url) 51 | .execute('return location.protocol') 52 | .then(protocol => { 53 | // eslint-disable-next-line no-bitwise 54 | if (!~[ 'http:', 'https:' ].indexOf(protocol)) { 55 | return new Promise(resolve => { 56 | const handle = () => { 57 | setTimeout(() => { 58 | this.get(url) 59 | .execute('return location.protocol') 60 | .then(protocol => { 61 | // eslint-disable-next-line no-bitwise 62 | if (~[ 'http:', 'https:' ].indexOf(protocol)) { 63 | setTimeout(resolve, 3000); 64 | } else { 65 | handle(); 66 | } 67 | }); 68 | }, 1000); 69 | }; 70 | handle(); 71 | }); 72 | } 73 | }); 74 | }); 75 | 76 | /** 77 | * Save screenshots to ./screenshots. 78 | * @function saveScreenshots 79 | * @summary Support: Web(WebView) 80 | * @type utility 81 | * @return {Promise.} 82 | */ 83 | wd.addPromiseChainMethod('saveScreenshots', function(context, params) { 84 | const filepath = path.join(reportsPath, 'screenshots', `${uuid()}.png`); 85 | mkdir(path.dirname(filepath)); 86 | 87 | return this.saveScreenshot(filepath, params).then(() => { 88 | appendToContext(context, `${path.relative(reportsPath, filepath)}`); 89 | }); 90 | }); 91 | 92 | /** 93 | * Save videos to ./screenshots. 94 | * @function saveVideos 95 | * @summary Support: Web(WebView) 96 | * @type utility 97 | * @return {Promise.} 98 | */ 99 | wd.addPromiseChainMethod('saveVideos', function(context, params = {}) { 100 | params.video = true; 101 | return this.saveScreenshot(null, params).then(filepath => { 102 | appendToContext(context, `${path.relative(reportsPath, filepath)}`); 103 | }); 104 | }); 105 | 106 | /** 107 | * Type content to input element. 108 | * @function formInput 109 | * @summary Support: Web(WebView) 110 | * @param {string} content of type. 111 | * @type utility 112 | * @return {Promise.} 113 | */ 114 | wd.addPromiseChainMethod('formInput', function(string) { 115 | const list = Array.prototype.slice.call(string); 116 | 117 | if (!list.length) { 118 | return this; 119 | } 120 | 121 | let value = ''; 122 | 123 | return Promise.each(list, item => { 124 | value += item; 125 | const script = ` 126 | (function() { 127 | var element = window.__macaca_current_element; 128 | var setValue = Object.getOwnPropertyDescriptor( 129 | window[element.constructor.name].prototype, 130 | 'value' 131 | ).set; 132 | setValue.call(element, ${safeQuote(value)}); 133 | var event = document.createEvent('Event'); 134 | event.initEvent('input', true, true); 135 | element.dispatchEvent(event); 136 | })() 137 | `; 138 | return this.execute(script).sleep(10); 139 | }); 140 | }); 141 | 142 | /** 143 | * Type content to a contentEditable element. 144 | * @function elementInput 145 | * @summary Support: Web(WebView) 146 | * @param {string} content of type. 147 | * @type utility 148 | * @return {Promise.} 149 | */ 150 | wd.addPromiseChainMethod('elementInput', function(string) { 151 | const list = Array.prototype.slice.call(string); 152 | 153 | if (!list.length) { 154 | return this; 155 | } 156 | 157 | let value = ''; 158 | 159 | return Promise.each(list, item => { 160 | value += item; 161 | const script = ` 162 | (function() { 163 | var element = window.__macaca_current_element; 164 | var setValue = Object.getOwnPropertyDescriptor( 165 | window.Element.prototype, 166 | 'innerHTML' 167 | ).set; 168 | setValue.call(element, ${safeQuote(value)}); 169 | var event = document.createEvent('Event'); 170 | event.initEvent('input', true, true); 171 | element.dispatchEvent(event); 172 | })() 173 | `; 174 | return this.execute(script).sleep(10); 175 | }); 176 | }); 177 | 178 | /** 179 | * Simulate the dom event. 180 | * @function domEvent 181 | * @summary Support: Web(WebView) 182 | * @param {string} eventType - type of the event. 183 | * @param {object} options - options of the event. 184 | * @see https://github.com/macacajs/dom-event-simulate#support-events 185 | * @type utility 186 | * @return {Promise.} 187 | */ 188 | wd.addPromiseChainMethod('domEvent', function(eventName, options) { 189 | const uuid = Date.now(); 190 | const script = ` 191 | ${domEventLibSource} 192 | var _element_${uuid} = window.__macaca_current_element; 193 | _macaca_simulate.domEvent(_element_${uuid}, '${eventName}', ${JSON.stringify( 194 | options 195 | )}); 196 | `; 197 | return this.execute(script); 198 | }); 199 | }; 200 | -------------------------------------------------------------------------------- /lib/helper/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const extendsMixIn = wd => { 4 | require('../next')(wd); 5 | require('./element')(wd); 6 | require('./assert')(wd); 7 | require('./macaca-datahub')(wd); 8 | require('./coverage')(wd); 9 | require('./reporter')(wd); 10 | require('macaca-ai-engine/lib/wd-mixin')(wd); 11 | }; 12 | 13 | function initDriverServer(wd, options = {}) { 14 | extendsMixIn(wd); 15 | 16 | const driver = wd.promiseChainRemote({ 17 | host: 'localhost', 18 | port: options.macacaServerPort || process.env.MACACA_SERVER_PORT || 3456, 19 | }); 20 | 21 | driver.configureHttp({ 22 | timeout: 20E3, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | 27 | const webpackDevServerPort = options.port || 8080; 28 | 29 | const BASE_URL = `http://127.0.0.1:${webpackDevServerPort}`; 30 | 31 | return { 32 | driver, 33 | BASE_URL, 34 | webpackDevServerPort, 35 | }; 36 | } 37 | 38 | module.exports = initDriverServer; 39 | module.exports.extendsMixIn = extendsMixIn; 40 | -------------------------------------------------------------------------------- /lib/helper/macaca-datahub.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const DataHubClient = require('datahub-nodejs-sdk'); 4 | 5 | const datahubClient = new DataHubClient(); 6 | 7 | module.exports = wd => { 8 | /** 9 | * Switch scene with DataHub. 10 | * @function switchScene 11 | * @summary Support: Web(WebView) 12 | * @param {object} options. 13 | * @type datahub 14 | * @return {Promise.} 15 | */ 16 | wd.addPromiseChainMethod('switchScene', function(args) { 17 | return datahubClient.switchScene(args); 18 | }); 19 | /** 20 | * Switch multi-scene with DataHub. 21 | * @function switchMultiScenes 22 | * @summary Support: Web(WebView) 23 | * @param {array} the scene list. 24 | * @type datahub 25 | * @return {Promise.} 26 | */ 27 | wd.addPromiseChainMethod('switchMultiScenes', function(args) { 28 | return datahubClient.switchMultiScenes(args); 29 | }); 30 | /** 31 | * Switch all scenes with DataHub. 32 | * @function switchAllScenes 33 | * @summary Support: Web(WebView) 34 | * @param {object} options. 35 | * @type datahub 36 | * @return {Promise.} 37 | */ 38 | wd.addPromiseChainMethod('switchAllScenes', function(args) { 39 | return datahubClient.switchAllScenes(args); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/helper/reporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const _ = require('xutil'); 5 | const { opn, platform, isExistedFile } = _; 6 | 7 | const cwd = process.cwd(); 8 | 9 | module.exports = wd => { 10 | 11 | /** 12 | * Open all reporters in browser. 13 | * @function openReporter 14 | * @summary Support: Web(WebView) 15 | * @param {boolean} if open. 16 | * @type utility 17 | * @return {Promise.} 18 | */ 19 | wd.addPromiseChainMethod('openReporter', function(open) { 20 | if (!open || !platform.isOSX) { 21 | return this; 22 | } 23 | 24 | const file1 = path.join(cwd, 'reports', 'index.html'); 25 | const file2 = path.join(cwd, 'coverage', 'index.html'); 26 | 27 | if (isExistedFile(file1)) { 28 | opn(file1); 29 | } 30 | 31 | if (isExistedFile(file2)) { 32 | opn(file2); 33 | } 34 | 35 | return this; 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/macaca-wd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const helper = require('./helper'); 4 | const wd = require('../wd/lib/main'); 5 | 6 | module.exports = wd; 7 | module.exports.helper = helper; 8 | module.exports.webpackHelper = helper(wd); 9 | -------------------------------------------------------------------------------- /lib/next/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = wd => { 4 | [ 5 | 'pageIframe', 6 | ].forEach(method => { 7 | wd.addPromiseChainMethod(method, function(...params) { 8 | const [ index, func, ...args ] = params; 9 | return this.next(method, [{ index, func, args }]); 10 | }); 11 | }); 12 | 13 | [ 14 | 'page', 15 | 'pagePopup', 16 | 'locator', 17 | 'browser', 18 | 'browserType', 19 | ].forEach(method => { 20 | wd.addPromiseChainMethod(method, function(...params) { 21 | const [ func, ...args ] = params; 22 | return this.next(method, [{ func, args }]); 23 | }); 24 | }); 25 | 26 | [ 27 | 'fileChooser', 28 | 'elementStatus', 29 | ].forEach(method => { 30 | wd.addPromiseChainMethod(method, function(...params) { 31 | return this.next(method, params); 32 | }); 33 | }); 34 | 35 | [ 36 | 'mouse', 37 | 'keyboard', 38 | ].forEach(method => { 39 | wd.addPromiseChainMethod(method, function(...params) { 40 | const [ type, ...args ] = params; 41 | return this.next(method, [{ type, args }]); 42 | }); 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /lib/web/react-router-helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const _ = require('lodash'); 5 | const path = require('path'); 6 | const { matchPath } = require('react-router'); 7 | 8 | const cwd = process.cwd(); 9 | 10 | exports.getRouter = (data, options = {}) => { 11 | const targetDirList = options.targetDirList; 12 | 13 | const routerWalker = (routes, parentRoute, handle) => { 14 | _.map(routes, (currentRoute, currentIndex) => { 15 | if (currentRoute) { 16 | handle(currentRoute, parentRoute, currentIndex); 17 | if (currentRoute.routes || currentRoute.childRoutes) { 18 | routerWalker(currentRoute.routes || currentRoute.childRoutes, currentRoute, handle); 19 | } 20 | } 21 | }); 22 | }; 23 | 24 | const fileWalkerSync = (rootDir, handle, options = {}) => { 25 | fs 26 | .readdirSync(rootDir) 27 | .forEach(file => { 28 | const currentDir = path.join(rootDir, file); 29 | const isDirectory = fs.existsSync(currentDir) && fs.statSync(currentDir).isDirectory(); 30 | if (isDirectory) { 31 | fileWalkerSync(currentDir, handle, options); 32 | } else { 33 | handle(currentDir); 34 | } 35 | }); 36 | }; 37 | 38 | const cleaner = list => { 39 | const res = []; 40 | list.forEach(item => { 41 | if (item) { 42 | if (item.routes) { 43 | item.routes = cleaner(item.routes); 44 | } else if (item.childRoutes) { 45 | item.childRoutes = cleaner(item.childRoutes); 46 | } 47 | res.push(item); 48 | } 49 | }); 50 | return res; 51 | }; 52 | 53 | routerWalker(data, data, (currentRoute, parentRoute, currentIndex) => { 54 | if (parentRoute && parentRoute.path) { 55 | currentRoute._path = currentRoute.path; 56 | currentRoute.__path = `${parentRoute.__path || parentRoute.path}${path.sep}${currentRoute.path}`; 57 | currentRoute.path = `${parentRoute.__path || parentRoute.path}${path.sep}${currentRoute.path}`; 58 | } 59 | let isMatched = false; 60 | _.map(targetDirList, currentDir => { 61 | const targetDir = path.join(cwd, currentDir); 62 | fileWalkerSync(targetDir, file => { 63 | const content = fs.readFileSync(file, 'utf8'); 64 | const matchReg = options.matchReg; 65 | let temp = null; 66 | while ((temp = matchReg.exec(content)) !== null) { 67 | let pathname = temp[1]; 68 | if (!currentRoute.path.startsWith(path.sep) && pathname.startsWith(path.sep)) { 69 | pathname = pathname.slice(1); 70 | } 71 | if (pathname.includes('?')) { 72 | pathname = pathname.split('?')[0]; 73 | } 74 | isMatched = matchPath(pathname, currentRoute) || isMatched; 75 | } 76 | }, { 77 | extname: '.js', 78 | }); 79 | }); 80 | currentRoute.path = currentRoute._path || currentRoute.path; 81 | if (!isMatched) { 82 | if (parentRoute.routes) { 83 | parentRoute.routes[currentIndex] = null; 84 | } else if (parentRoute.childRoutes) { 85 | parentRoute.childRoutes[currentIndex] = null; 86 | } else { 87 | parentRoute[currentIndex] = null; 88 | } 89 | } else { 90 | // console.log(currentRoute); 91 | } 92 | }); 93 | return cleaner(data); 94 | }; 95 | 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macaca-wd", 3 | "version": "4.3.6", 4 | "description": "Macaca webdirver API for Node.js", 5 | "keywords": [ 6 | "macaca", 7 | "webdriver", 8 | "wd", 9 | "Node.js" 10 | ], 11 | "files": [ 12 | "lib/**/*.js", 13 | "wd/**/*.js", 14 | "wd/package.json" 15 | ], 16 | "main": "./lib/macaca-wd.js", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/macacajs/macaca-wd.git" 20 | }, 21 | "dependencies": { 22 | "archiver": "~0.14.0", 23 | "async": "~1.0.0", 24 | "bluebird": "^3.5.1", 25 | "chai": "^4.1.2", 26 | "commander": "^2.19.0", 27 | "datahub-nodejs-sdk": "2", 28 | "dom-event-simulate": "^1.0.0", 29 | "lodash": "~3.9.3", 30 | "macaca-ai-engine": "*", 31 | "macaca-reporter": "1", 32 | "mkdirp": "^0.5.1", 33 | "q": "~1.4.1", 34 | "react-router": "4", 35 | "request": "~2.85.0", 36 | "underscore.string": "~3.0.3", 37 | "vargs": "~0.1.0", 38 | "webdriver-keycode": "^1.0.0", 39 | "xlogger": "^1.0.6", 40 | "xutil": "1" 41 | }, 42 | "devDependencies": { 43 | "eslint": "8", 44 | "eslint-config-egg": "^12.2.1", 45 | "git-contributor": "1", 46 | "husky": "^1.3.1", 47 | "jsdom": "^11.10.0", 48 | "koa": "^2.7.0", 49 | "koa-bodyparser": "^4.2.1", 50 | "macaca-ecosystem": "1", 51 | "mocha": "*", 52 | "nyc": "^11.6.0", 53 | "stoppable": "^1.1.0", 54 | "vuepress": "^1.9.8" 55 | }, 56 | "scripts": { 57 | "docs:dev": "vuepress dev docs", 58 | "docs:build": "vuepress build docs", 59 | "test": "mocha", 60 | "lint": "eslint --fix --ext js,ts lib test wd/lib", 61 | "contributor": "git-contributor" 62 | }, 63 | "husky": { 64 | "hooks": { 65 | "pre-commit": "npm run lint" 66 | } 67 | }, 68 | "homepage": "https://github.com/macacajs/macaca-wd", 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /test/alert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/alert.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | beforeEach(async () => { 33 | await driver.init({ 34 | platformName: 'desktop', 35 | browserName: 'chrome', 36 | }); 37 | }); 38 | 39 | /** 40 | * https://macacajs.github.io/macaca-wd/#acceptAlert 41 | */ 42 | describe('acceptAlert', async () => { 43 | it('should work', async () => { 44 | await driver.acceptAlert(); 45 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/accept_alert'); 46 | assert.equal(server.ctx.method, 'POST'); 47 | assert.deepEqual(server.ctx.request.body, {}); 48 | assert.deepEqual(server.ctx.response.body, { 49 | sessionId: 'sessionId', 50 | status: 0, 51 | value: '', 52 | }); 53 | }); 54 | }); 55 | 56 | /** 57 | * https://macacajs.github.io/macaca-wd/#alertKeys 58 | */ 59 | describe('alertKeys', async () => { 60 | it('should work', async () => { 61 | await driver.alertKeys('test_key'); 62 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/alert_text'); 63 | assert.equal(server.ctx.method, 'POST'); 64 | assert.deepEqual(server.ctx.request.body, { text: 'test_key' }); 65 | assert.deepEqual(server.ctx.response.body, { 66 | sessionId: 'sessionId', 67 | status: 0, 68 | value: '', 69 | }); 70 | }); 71 | }); 72 | 73 | /** 74 | * https://macacajs.github.io/macaca-wd/#alertText 75 | */ 76 | describe('alertText', async () => { 77 | it('should work', async () => { 78 | await driver.alertText(); 79 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/alert_text'); 80 | assert.equal(server.ctx.method, 'GET'); 81 | assert.deepEqual(server.ctx.response.body, { 82 | sessionId: 'sessionId', 83 | status: 0, 84 | value: '', 85 | }); 86 | }); 87 | }); 88 | 89 | /** 90 | * https://macacajs.github.io/macaca-wd/#dismissAlert 91 | */ 92 | describe('dismissAlert', async () => { 93 | it('should work', async () => { 94 | await driver.dismissAlert(); 95 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/dismiss_alert'); 96 | assert.equal(server.ctx.method, 'POST'); 97 | assert.deepEqual(server.ctx.request.body, {}); 98 | assert.deepEqual(server.ctx.response.body, { 99 | sessionId: 'sessionId', 100 | status: 0, 101 | value: '', 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/assert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | const { elFuncFullType, elFuncSuffix } = require('../wd/lib/utils'); 7 | 8 | const wd = require('../lib/macaca-wd'); 9 | 10 | describe('test/asserter.test.js', function() { 11 | let driver, 12 | server; 13 | 14 | const mockServer = async (mockKey, mockValue) => { 15 | server = new Server(); 16 | server.mock(mockKey, mockValue); 17 | server.start(); 18 | const remoteConfig = { 19 | host: 'localhost', 20 | port: 3456, 21 | }; 22 | driver = wd.promiseChainRemote(remoteConfig); 23 | driver.configureHttp({ 24 | timeout: 20 * 1000, 25 | retries: 5, 26 | retryDelay: 5, 27 | }); 28 | }; 29 | 30 | describe('single method tests', () => { 31 | afterEach(() => { 32 | server.stop(); 33 | }); 34 | 35 | beforeEach(async () => { 36 | await driver.init({ 37 | platformName: 'desktop', 38 | browserName: 'chrome', 39 | }); 40 | }); 41 | 42 | /** 43 | * https://macacajs.github.io/macaca-wd/#assertAttribute 44 | */ 45 | describe('assertAttribute', async () => { 46 | before(async () => { 47 | mockServer('ctx.body', { 48 | sessionId: 'sessionId', 49 | status: 0, 50 | value: 'someClass', 51 | }); 52 | }); 53 | 54 | it('should work', async () => { 55 | await driver.assertAttribute('class', 'someClass'); 56 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 57 | assert.equal(server.ctx.method, 'POST'); 58 | const { script, args } = server.ctx.request.body; 59 | assert.equal( 60 | script, 61 | "return window.__macaca_current_element.getAttribute('class')" 62 | ); 63 | assert.equal(args.length, 0); 64 | }); 65 | }); 66 | 67 | /** 68 | * https://macacajs.github.io/macaca-wd/#assertTitle 69 | */ 70 | describe('assertTitle', async () => { 71 | before(async () => { 72 | await mockServer('ctx.body', { 73 | sessionId: 'sessionId', 74 | status: 0, 75 | value: 'My Title', 76 | }); 77 | }); 78 | 79 | it('should work', async () => { 80 | await driver.assertTitle('My Title'); 81 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/title'); 82 | assert.equal(server.ctx.method, 'GET'); 83 | }); 84 | }); 85 | 86 | /** 87 | * https://macacajs.github.io/macaca-wd/#hasElement 88 | */ 89 | describe('hasElement', async () => { 90 | before(async () => { 91 | await mockServer('ctx.body', { 92 | sessionId: 'sessionId', 93 | status: 0, 94 | value: [{ ELEMENT: 1 }, { ELEMENT: 2 }], 95 | }); 96 | }); 97 | 98 | it('should work', async () => { 99 | await driver.hasElement('class', 'myClass'); 100 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/elements'); 101 | assert.equal(server.ctx.method, 'POST'); 102 | const { using, value } = server.ctx.request.body; 103 | assert.equal(using, 'class'); 104 | assert.equal(value, 'myClass'); 105 | }); 106 | }); 107 | }); 108 | 109 | /** 110 | * https://macacajs.github.io/macaca-wd/#hasElementByClassName 111 | * https://macacajs.github.io/macaca-wd/#hasElementByCss 112 | * https://macacajs.github.io/macaca-wd/#hasElementById 113 | * https://macacajs.github.io/macaca-wd/#hasElementByName 114 | * https://macacajs.github.io/macaca-wd/#hasElementByPartialLinkText 115 | * https://macacajs.github.io/macaca-wd/#hasElementByTagName 116 | * https://macacajs.github.io/macaca-wd/#hasElementByXPath 117 | */ 118 | describe('hasElement type suffix method tests', async () => { 119 | afterEach(() => { 120 | server.stop(); 121 | }); 122 | 123 | beforeEach(async () => { 124 | await mockServer('ctx.body', { 125 | sessionId: 'sessionId', 126 | status: 0, 127 | value: [{ ELEMENT: 1 }, { ELEMENT: 2 }], 128 | }); 129 | await driver.init({ 130 | platformName: 'desktop', 131 | browserName: 'chrome', 132 | }); 133 | }); 134 | 135 | const tests = [ 136 | 'class name', 137 | 'css', 138 | 'id', 139 | 'link text', 140 | 'name', 141 | 'partial link text', 142 | 'tag name', 143 | 'xpath', 144 | ].map(type => { 145 | return { 146 | functionSuffix: `hasElement${elFuncSuffix(type)}`, 147 | fullType: elFuncFullType(type), 148 | }; 149 | }); 150 | 151 | tests.forEach(function(test) { 152 | it(`${test.functionSuffix} should work`, async () => { 153 | await driver[test.functionSuffix]('myValue'); 154 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/elements'); 155 | assert.equal(server.ctx.method, 'POST'); 156 | const { using, value } = server.ctx.request.body; 157 | assert.equal(using, test.fullType); 158 | assert.equal(value, 'myValue'); 159 | }); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /test/browser.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/browser.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | beforeEach(async () => { 33 | await driver.init({ 34 | platformName: 'desktop', 35 | browserName: 'chrome', 36 | }); 37 | }); 38 | 39 | /** 40 | * https://macacajs.github.io/macaca-wd/#back 41 | */ 42 | describe('back', async () => { 43 | it('should work', async () => { 44 | await driver.back(); 45 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/back'); 46 | assert.equal(server.ctx.method, 'POST'); 47 | assert.deepEqual(server.ctx.response.body, { 48 | sessionId: 'sessionId', 49 | status: 0, 50 | value: '', 51 | }); 52 | }); 53 | }); 54 | 55 | /** 56 | * https://macacajs.github.io/macaca-wd/#forward 57 | */ 58 | describe('forward', async () => { 59 | it('should work', async () => { 60 | await driver.forward(); 61 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/forward'); 62 | assert.equal(server.ctx.method, 'POST'); 63 | assert.deepEqual(server.ctx.response.body, { 64 | sessionId: 'sessionId', 65 | status: 0, 66 | value: '', 67 | }); 68 | }); 69 | }); 70 | 71 | /** 72 | * https://macacajs.github.io/macaca-wd/#get 73 | */ 74 | describe('get', async () => { 75 | it('should work', async () => { 76 | await driver.get('https://github.com'); 77 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/url'); 78 | assert.equal(server.ctx.method, 'POST'); 79 | assert.deepEqual(server.ctx.request.body.url, 'https://github.com'); 80 | assert.deepEqual(server.ctx.response.body, { 81 | sessionId: 'sessionId', 82 | status: 0, 83 | value: '', 84 | }); 85 | }); 86 | }); 87 | 88 | /** 89 | * https://macacajs.github.io/macaca-wd/#maximize 90 | */ 91 | describe('maximize', async () => { 92 | it('should work', async () => { 93 | await driver.maximize(); 94 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/window/current/maximize'); 95 | assert.equal(server.ctx.method, 'POST'); 96 | assert.deepEqual(server.ctx.response.body, { 97 | sessionId: 'sessionId', 98 | status: 0, 99 | value: '', 100 | }); 101 | }); 102 | }); 103 | 104 | /** 105 | * https://macacajs.github.io/macaca-wd/#maximize 106 | */ 107 | describe('refresh', async () => { 108 | it('should work', async () => { 109 | await driver.refresh(); 110 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/refresh'); 111 | assert.equal(server.ctx.method, 'POST'); 112 | assert.deepEqual(server.ctx.response.body, { 113 | sessionId: 'sessionId', 114 | status: 0, 115 | value: '', 116 | }); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/context.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macacajs/macaca-wd/565ad3f0e1c5458daecb450cbc62c692b7fa0be0/test/context.test.js -------------------------------------------------------------------------------- /test/cookie.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/cookie.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | beforeEach(async () => { 33 | await driver.init({ 34 | platformName: 'desktop', 35 | browserName: 'chrome', 36 | }); 37 | }); 38 | 39 | /** 40 | * https://macacajs.github.io/macaca-wd/#allCookies 41 | */ 42 | describe('allCookies', async () => { 43 | it('should work', async () => { 44 | await driver.allCookies(); 45 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/cookie'); 46 | assert.equal(server.ctx.method, 'GET'); 47 | }); 48 | }); 49 | 50 | /** 51 | * https://macacajs.github.io/macaca-wd/#deleteAllCookies 52 | */ 53 | describe('deleteAllCookies', async () => { 54 | it('should work', async () => { 55 | await driver.deleteAllCookies(); 56 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/cookie'); 57 | assert.equal(server.ctx.method, 'DELETE'); 58 | assert.deepEqual(server.ctx.request.body, {}); 59 | assert.deepEqual(server.ctx.response.body, { 60 | sessionId: 'sessionId', 61 | status: 0, 62 | value: '', 63 | }); 64 | }); 65 | }); 66 | 67 | /** 68 | * https://macacajs.github.io/macaca-wd/#deleteCookie 69 | */ 70 | describe('deleteCookie', async () => { 71 | it('should work', async () => { 72 | await driver.deleteCookie('test_cookie'); 73 | assert.equal( 74 | server.ctx.url, 75 | '/wd/hub/session/sessionId/cookie/test_cookie' 76 | ); 77 | assert.equal(server.ctx.method, 'DELETE'); 78 | assert.deepEqual(server.ctx.request.body, {}); 79 | assert.deepEqual(server.ctx.response.body, { 80 | sessionId: 'sessionId', 81 | status: 0, 82 | value: '', 83 | }); 84 | }); 85 | }); 86 | 87 | /** 88 | * https://macacajs.github.io/macaca-wd/#setCookie 89 | */ 90 | describe('setCookie', async () => { 91 | it('should work', async () => { 92 | await driver.setCookie('test_cookie'); 93 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/cookie'); 94 | assert.equal(server.ctx.method, 'POST'); 95 | assert.deepEqual(server.ctx.request.body, { 96 | cookie: 'test_cookie', 97 | }); 98 | assert.deepEqual(server.ctx.response.body, { 99 | sessionId: 'sessionId', 100 | status: 0, 101 | value: '', 102 | }); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/datahub.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macacajs/macaca-wd/565ad3f0e1c5458daecb450cbc62c692b7fa0be0/test/datahub.test.js -------------------------------------------------------------------------------- /test/element.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macacajs/macaca-wd/565ad3f0e1c5458daecb450cbc62c692b7fa0be0/test/element.test.js -------------------------------------------------------------------------------- /test/excecute.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/execute.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#source 34 | */ 35 | describe('execute', async () => { 36 | it('should work', async () => { 37 | await driver.execute('return window'); 38 | assert.equal(server.ctx.method, 'POST'); 39 | assert.equal(server.ctx.url, '/wd/hub/session/execute'); 40 | assert.deepEqual(server.ctx.request.body, { 41 | args: [], 42 | script: 'return window', 43 | }); 44 | assert.deepEqual(server.ctx.response.body, { 45 | sessionId: 'sessionId', 46 | status: 0, 47 | value: '', 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const Koa = require('koa'); 5 | const bodyParser = require('koa-bodyparser'); 6 | const stoppable = require('stoppable'); 7 | const _ = require('lodash'); 8 | 9 | class Server { 10 | constructor(options) { 11 | this.options = Object.assign( 12 | { 13 | port: 3456, 14 | }, 15 | options || {} 16 | ); 17 | this.app = new Koa(); 18 | this.app.use(bodyParser()); 19 | } 20 | 21 | mock(key, value) { 22 | this.app.use(async (ctx, next) => { 23 | await next(); 24 | this.ctx = ctx; 25 | _.set(this, key, value); 26 | }); 27 | this.mocked = true; 28 | } 29 | 30 | start() { 31 | if (!this.mocked) { 32 | this.app.use(async (ctx, next) => { 33 | await next(); 34 | this.ctx = ctx; 35 | this.ctx.body = { 36 | sessionId: 'sessionId', 37 | status: 0, 38 | value: '', 39 | }; 40 | }); 41 | } 42 | this.server = stoppable( 43 | http.createServer(this.app.callback()).listen(this.options.port) 44 | ); 45 | } 46 | 47 | stop() { 48 | this.server.stop(); 49 | } 50 | } 51 | 52 | module.exports.Server = Server; 53 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -------------------------------------------------------------------------------- /test/others.test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macacajs/macaca-wd/565ad3f0e1c5458daecb450cbc62c692b7fa0be0/test/others.test.js -------------------------------------------------------------------------------- /test/screenshot.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/screenshot.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#saveScreenshot 34 | */ 35 | describe('saveScreenshot', async () => { 36 | it('should work', async () => { 37 | await driver.saveScreenshot(); 38 | assert.equal(server.ctx.method, 'GET'); 39 | assert.equal(server.ctx.url, '/wd/hub/session/screenshot'); 40 | assert.deepEqual(server.ctx.request.body, {}); 41 | assert.deepEqual(server.ctx.response.body, { 42 | sessionId: 'sessionId', 43 | status: 0, 44 | value: '', 45 | }); 46 | }); 47 | }); 48 | 49 | /** 50 | * https://macacajs.github.io/macaca-wd/#takeScreenshot 51 | */ 52 | describe('takeScreenshot', async () => { 53 | it('should work', async () => { 54 | await driver.takeScreenshot(); 55 | assert.equal(server.ctx.method, 'GET'); 56 | assert.equal(server.ctx.url, '/wd/hub/session/screenshot'); 57 | assert.deepEqual(server.ctx.request.body, {}); 58 | assert.deepEqual(server.ctx.response.body, { 59 | sessionId: 'sessionId', 60 | status: 0, 61 | value: '', 62 | }); 63 | }); 64 | }); 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /test/session.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/session.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#init 34 | */ 35 | describe('init', async () => { 36 | it('should work', async () => { 37 | await driver.init({ 38 | platformName: 'desktop', 39 | browserName: 'chrome', 40 | }); 41 | assert.equal(server.ctx.method, 'POST'); 42 | assert.equal(server.ctx.url, '/wd/hub/session'); 43 | assert.deepEqual(server.ctx.request.body, { 44 | desiredCapabilities: { 45 | platformName: 'desktop', 46 | browserName: 'chrome', 47 | version: '', 48 | javascriptEnabled: true, 49 | platform: 'ANY', 50 | }, 51 | }); 52 | assert.deepEqual(server.ctx.response.body, { 53 | sessionId: 'sessionId', 54 | status: 0, 55 | value: '', 56 | }); 57 | }); 58 | }); 59 | 60 | /** 61 | * https://macacajs.github.io/macaca-wd/#quit 62 | */ 63 | describe('quit', async () => { 64 | beforeEach(async () => { 65 | await driver.init({ 66 | platformName: 'desktop', 67 | browserName: 'chrome', 68 | }); 69 | }); 70 | it('should work', async () => { 71 | await driver.quit(); 72 | assert.equal(server.ctx.method, 'DELETE'); 73 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId'); 74 | assert.deepEqual(server.ctx.response.body, { 75 | sessionId: 'sessionId', 76 | status: 0, 77 | value: '', 78 | }); 79 | }); 80 | }); 81 | 82 | /** 83 | * https://macacajs.github.io/macaca-wd/#sessions 84 | */ 85 | describe('sessions', async () => { 86 | beforeEach(async () => { 87 | await driver.init({ 88 | platformName: 'desktop', 89 | browserName: 'chrome', 90 | }); 91 | }); 92 | it('should work', async () => { 93 | await driver.sessions(); 94 | assert.equal(server.ctx.method, 'GET'); 95 | assert.equal(server.ctx.url, '/wd/hub/sessions'); 96 | assert.deepEqual(server.ctx.response.body, { 97 | sessionId: 'sessionId', 98 | status: 0, 99 | value: '', 100 | }); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/source.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/source.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#source 34 | */ 35 | describe('source', async () => { 36 | it('should work', async () => { 37 | await driver.source(); 38 | assert.equal(server.ctx.method, 'GET'); 39 | assert.equal(server.ctx.url, '/wd/hub/session/source'); 40 | assert.deepEqual(server.ctx.request.body, {}); 41 | assert.deepEqual(server.ctx.response.body, { 42 | sessionId: 'sessionId', 43 | status: 0, 44 | value: '', 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /test/title.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/title.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#title 34 | */ 35 | describe('title', async () => { 36 | it('should work', async () => { 37 | await driver.title(); 38 | assert.equal(server.ctx.method, 'GET'); 39 | assert.equal(server.ctx.url, '/wd/hub/session/title'); 40 | assert.deepEqual(server.ctx.request.body, {}); 41 | assert.deepEqual(server.ctx.response.body, { 42 | sessionId: 'sessionId', 43 | status: 0, 44 | value: '', 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /test/url.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/url.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | /** 33 | * https://macacajs.github.io/macaca-wd/#url 34 | */ 35 | describe('url', async () => { 36 | it('should work', async () => { 37 | await driver.url(); 38 | assert.equal(server.ctx.method, 'GET'); 39 | assert.equal(server.ctx.url, '/wd/hub/session/url'); 40 | assert.deepEqual(server.ctx.request.body, {}); 41 | assert.deepEqual(server.ctx.response.body, { 42 | sessionId: 'sessionId', 43 | status: 0, 44 | value: '', 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /test/utility.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/utility.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | beforeEach(async () => { 33 | await driver.init({ 34 | platformName: 'desktop', 35 | browserName: 'chrome', 36 | }); 37 | }); 38 | 39 | /** 40 | * https://macacajs.github.io/macaca-wd/#coverage 41 | */ 42 | describe('coverage', async () => { 43 | it('should work', async () => { 44 | await driver.coverage(); 45 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 46 | assert.equal(server.ctx.method, 'POST'); 47 | assert.deepEqual(server.ctx.response.body, { 48 | sessionId: 'sessionId', 49 | status: 0, 50 | value: '', 51 | }); 52 | }); 53 | }); 54 | 55 | /** 56 | * https://macacajs.github.io/macaca-wd/#domEvent 57 | */ 58 | describe('domEvent', async () => { 59 | it('should work', async () => { 60 | await driver.domEvent(); 61 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 62 | assert.equal(server.ctx.method, 'POST'); 63 | assert.ok(server.ctx.request.body.script); 64 | assert.deepEqual(server.ctx.response.body, { 65 | sessionId: 'sessionId', 66 | status: 0, 67 | value: '', 68 | }); 69 | }); 70 | }); 71 | 72 | /** 73 | * https://macacajs.github.io/macaca-wd/#elementInput 74 | */ 75 | describe('elementInput', async () => { 76 | it('should work', async () => { 77 | await driver.elementInput('content'); 78 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 79 | assert.equal(server.ctx.method, 'POST'); 80 | assert.ok(server.ctx.request.body.script); 81 | assert.deepEqual(server.ctx.response.body, { 82 | sessionId: 'sessionId', 83 | status: 0, 84 | value: '', 85 | }); 86 | }); 87 | }); 88 | 89 | /** 90 | * https://macacajs.github.io/macaca-wd/#formInput 91 | */ 92 | describe('formInput', async () => { 93 | it('should work', async () => { 94 | await driver.formInput('content'); 95 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 96 | assert.equal(server.ctx.method, 'POST'); 97 | assert.ok(server.ctx.request.body.script); 98 | assert.deepEqual(server.ctx.response.body, { 99 | sessionId: 'sessionId', 100 | status: 0, 101 | value: '', 102 | }); 103 | }); 104 | }); 105 | 106 | /** 107 | * https://macacajs.github.io/macaca-wd/#getUrl 108 | */ 109 | describe.skip('getUrl', async () => { 110 | it('should work', async () => { 111 | await driver.getUrl('https://github.com'); 112 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/execute'); 113 | assert.equal(server.ctx.method, 'POST'); 114 | assert.ok(server.ctx.request.body.script); 115 | assert.deepEqual(server.ctx.response.body, { 116 | sessionId: 'sessionId', 117 | status: 0, 118 | value: '', 119 | }); 120 | }); 121 | }); 122 | 123 | /** 124 | * https://macacajs.github.io/macaca-wd/#openReporter 125 | */ 126 | describe.skip('openReporter', async () => { 127 | it('should work', async () => { 128 | await driver.openReporter(false); 129 | }); 130 | }); 131 | 132 | /** 133 | * https://macacajs.github.io/macaca-wd/#saveScreenshots 134 | */ 135 | describe('saveScreenshots', async () => { 136 | afterEach(function() { 137 | return driver.saveScreenshots(this); 138 | }); 139 | it('should work', async () => { 140 | assert.ok(driver); 141 | }); 142 | }); 143 | 144 | /** 145 | * https://macacajs.github.io/macaca-wd/#elementStatus 146 | */ 147 | describe('elementStatus', async () => { 148 | it('should work', async () => { 149 | await driver.elementStatus(); 150 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 151 | assert.equal(server.ctx.method, 'POST'); 152 | assert.deepEqual(server.ctx.response.body, { 153 | sessionId: 'sessionId', 154 | status: 0, 155 | value: '', 156 | }); 157 | }); 158 | }); 159 | 160 | /** 161 | * https://macacajs.github.io/macaca-wd/#page 162 | */ 163 | describe('page', async () => { 164 | it('should work', async () => { 165 | await driver.page('url'); 166 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 167 | assert.equal(server.ctx.method, 'POST'); 168 | assert.deepEqual(server.ctx.response.body, { 169 | sessionId: 'sessionId', 170 | status: 0, 171 | value: '', 172 | }); 173 | }); 174 | }); 175 | 176 | /** 177 | * https://macacajs.github.io/macaca-wd/#pagePopup 178 | */ 179 | describe('pagePopup', async () => { 180 | it('should work', async () => { 181 | await driver.pagePopup('url'); 182 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 183 | assert.equal(server.ctx.method, 'POST'); 184 | assert.deepEqual(server.ctx.response.body, { 185 | sessionId: 'sessionId', 186 | status: 0, 187 | value: '', 188 | }); 189 | }); 190 | }); 191 | 192 | /** 193 | * https://macacajs.github.io/macaca-wd/#locator 194 | */ 195 | describe('locator', async () => { 196 | it('should work', async () => { 197 | await driver.locator(''); 198 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 199 | assert.equal(server.ctx.method, 'POST'); 200 | assert.deepEqual(server.ctx.response.body, { 201 | sessionId: 'sessionId', 202 | status: 0, 203 | value: '', 204 | }); 205 | }); 206 | }); 207 | 208 | /** 209 | * https://macacajs.github.io/macaca-wd/#pageIframe 210 | */ 211 | describe('pageIframe', async () => { 212 | it('should work', async () => { 213 | await driver.pageIframe('url'); 214 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 215 | assert.equal(server.ctx.method, 'POST'); 216 | assert.deepEqual(server.ctx.response.body, { 217 | sessionId: 'sessionId', 218 | status: 0, 219 | value: '', 220 | }); 221 | }); 222 | }); 223 | 224 | /** 225 | * https://macacajs.github.io/macaca-wd/#browser 226 | */ 227 | describe('browser', async () => { 228 | it('should work', async () => { 229 | await driver.browser('version'); 230 | assert.equal(server.ctx.url, '/wd/hub/session/sessionId/next'); 231 | assert.equal(server.ctx.method, 'POST'); 232 | assert.deepEqual(server.ctx.response.body, { 233 | sessionId: 'sessionId', 234 | status: 0, 235 | value: '', 236 | }); 237 | }); 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /test/window.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const { Server } = require('./helper'); 6 | 7 | const wd = require('../lib/macaca-wd'); 8 | 9 | describe('test/window.test.js', function() { 10 | let driver, 11 | server; 12 | before(() => { 13 | server = new Server(); 14 | server.start(); 15 | const remoteConfig = { 16 | host: 'localhost', 17 | port: 3456, 18 | }; 19 | 20 | driver = wd.promiseChainRemote(remoteConfig); 21 | driver.configureHttp({ 22 | timeout: 20 * 1000, 23 | retries: 5, 24 | retryDelay: 5, 25 | }); 26 | }); 27 | 28 | after(() => { 29 | server.stop(); 30 | }); 31 | 32 | 33 | /** 34 | * https://macacajs.github.io/macaca-wd/#close 35 | */ 36 | describe('close', async () => { 37 | it('should work', async () => { 38 | await driver.close(); 39 | assert.equal(server.ctx.method, 'DELETE'); 40 | assert.equal(server.ctx.url, '/wd/hub/session/window'); 41 | assert.deepEqual(server.ctx.request.body, {}); 42 | assert.deepEqual(server.ctx.response.body, { 43 | sessionId: 'sessionId', 44 | status: 0, 45 | value: '', 46 | }); 47 | }); 48 | }); 49 | 50 | /** 51 | * https://macacajs.github.io/macaca-wd/#getWindowSize 52 | */ 53 | describe('getWindowSize', async () => { 54 | it('should work', async () => { 55 | await driver.getWindowSize(); 56 | assert.equal(server.ctx.method, 'GET'); 57 | assert.equal(server.ctx.url, '/wd/hub/session/window/current/size'); 58 | assert.deepEqual(server.ctx.request.body, {}); 59 | assert.deepEqual(server.ctx.response.body, { 60 | sessionId: 'sessionId', 61 | status: 0, 62 | value: '', 63 | }); 64 | }); 65 | }); 66 | 67 | /** 68 | * https://macacajs.github.io/macaca-wd/#setWindowSize 69 | */ 70 | describe('setWindowSize', async () => { 71 | it('should work', async () => { 72 | await driver.setWindowSize(800, 600); 73 | assert.equal(server.ctx.method, 'POST'); 74 | assert.equal(server.ctx.url, '/wd/hub/session/window/current/size'); 75 | assert.deepEqual(server.ctx.request.body, { 76 | width: 800, 77 | height: 600, 78 | }); 79 | assert.deepEqual(server.ctx.response.body, { 80 | sessionId: 'sessionId', 81 | status: 0, 82 | value: '', 83 | }); 84 | }); 85 | }); 86 | 87 | /** 88 | * https://macacajs.github.io/macaca-wd/#window 89 | */ 90 | describe('window', async () => { 91 | it('should work', async () => { 92 | await driver.window(); 93 | assert.equal(server.ctx.method, 'POST'); 94 | assert.equal(server.ctx.url, '/wd/hub/session/window'); 95 | assert.deepEqual(server.ctx.request.body, {}); 96 | assert.deepEqual(server.ctx.response.body, { 97 | sessionId: 'sessionId', 98 | status: 0, 99 | value: '', 100 | }); 101 | }); 102 | }); 103 | 104 | /** 105 | * https://macacajs.github.io/macaca-wd/#windowHandle 106 | */ 107 | describe('windowHandle', async () => { 108 | it('should work', async () => { 109 | await driver.windowHandle(); 110 | assert.equal(server.ctx.method, 'GET'); 111 | assert.equal(server.ctx.url, '/wd/hub/session/window_handle'); 112 | assert.deepEqual(server.ctx.request.body, {}); 113 | assert.deepEqual(server.ctx.response.body, { 114 | sessionId: 'sessionId', 115 | status: 0, 116 | value: '', 117 | }); 118 | }); 119 | }); 120 | 121 | /** 122 | * https://macacajs.github.io/macaca-wd/#windowHandles 123 | */ 124 | describe('windowHandles', async () => { 125 | it('should work', async () => { 126 | await driver.windowHandles(); 127 | assert.equal(server.ctx.method, 'GET'); 128 | assert.equal(server.ctx.url, '/wd/hub/session/window_handles'); 129 | assert.deepEqual(server.ctx.request.body, {}); 130 | assert.deepEqual(server.ctx.response.body, { 131 | sessionId: 'sessionId', 132 | status: 0, 133 | value: '', 134 | }); 135 | }); 136 | }); 137 | 138 | 139 | /** 140 | * https://macacajs.github.io/macaca-wd/#initWindow 141 | */ 142 | describe('initWindow', async () => { 143 | it('should work', async () => { 144 | await driver.initWindow({ 145 | width: 800, 146 | height: 600, 147 | }); 148 | assert.equal( 149 | server.ctx.url, 150 | '/wd/hub/session' 151 | ); 152 | assert.equal(server.ctx.method, 'POST'); 153 | assert.deepEqual(server.ctx.response.body, { 154 | sessionId: 'sessionId', 155 | status: 0, 156 | value: '', 157 | }); 158 | }); 159 | }); 160 | }); 161 | 162 | -------------------------------------------------------------------------------- /wd/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /coverage 3 | .DS_Store 4 | tmp 5 | temp 6 | .npmignore 7 | *.log 8 | -------------------------------------------------------------------------------- /wd/LICENSE.APACHE2: -------------------------------------------------------------------------------- 1 | Apache License, Version 2.0 2 | 3 | Copyright (c) 2012 Sauce Labs 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /wd/browser-scripts/safe-execute-async.js: -------------------------------------------------------------------------------- 1 | /* jshint evil:true */ 2 | const args = Array.prototype.slice.call(arguments, 0); 3 | const code = args[0]; 4 | const fargs = args[1]; 5 | const done = args[2]; 6 | const wrap = function() { 7 | // eslint-disable-next-line no-eval 8 | return eval(code); 9 | }; 10 | fargs.push(done); 11 | wrap.apply(this, fargs); 12 | -------------------------------------------------------------------------------- /wd/browser-scripts/safe-execute.js: -------------------------------------------------------------------------------- 1 | /* jshint evil:true */ 2 | const args = Array.prototype.slice.call(arguments, 0); 3 | const code = args[0]; 4 | const fargs = args[1]; 5 | const wrap = function() { 6 | // eslint-disable-next-line no-eval 7 | return eval(code); 8 | }; 9 | 10 | wrap.apply(this, fargs); 11 | -------------------------------------------------------------------------------- /wd/browser-scripts/wait-for-cond-in-browser.js: -------------------------------------------------------------------------------- 1 | /* jshint evil:true */ 2 | const args = Array.prototype.slice.call(arguments, 0); 3 | const condExpr = args[0]; 4 | const timeout = args[1]; 5 | const poll = args[2]; 6 | const cb = args[3]; 7 | const waitForConditionImpl = function(conditionExpr, limit, poll, cb) { 8 | let res; 9 | if ((new Date().getTime()) < limit) { 10 | // eslint-disable-next-line no-eval 11 | res = eval(conditionExpr); 12 | if (res === true) { 13 | cb(res); 14 | } else { 15 | setTimeout(function() { 16 | waitForConditionImpl(conditionExpr, limit, poll, cb); 17 | }, poll); 18 | } 19 | } else { 20 | // eslint-disable-next-line no-eval 21 | res = eval(conditionExpr); 22 | return cb(res); 23 | } 24 | }; 25 | 26 | const limit = (new Date().getTime()) + timeout; 27 | waitForConditionImpl(condExpr, limit, poll, cb); 28 | -------------------------------------------------------------------------------- /wd/doc/deprecated-chain.md: -------------------------------------------------------------------------------- 1 | ## Chain Api 2 | 3 | A chain api is also available. Code sample is [here](https://github.com/admc/wd/blob/master/examples/example.chain.chrome.js). 4 | 5 | ### Injecting command to the chain 6 | 7 | As [queue](https://github.com/caolan/async#queue) implementation that we're using has some limitations, a special helper method *next* was added. It allows you to inject new calls to the execution chain inside callbacks. 8 | 9 | #### Example 1 - the problem 10 | 11 | ```javascript 12 | browser 13 | .chain() 14 | // ... 15 | .elementById('i am a link', function(err, el) { 16 | // following call will be executed apart from the current execution chain 17 | // you won't be able to pass results further in chain 18 | // and it may cause racing conditions in your script 19 | browser.clickElement(el, function() { 20 | console.log("did the click!"); 21 | }); 22 | }) 23 | // ... 24 | ``` 25 | 26 | #### Example 2 - solution, use *next* 27 | 28 | ```javascript 29 | browser 30 | .chain() 31 | // ... 32 | .elementById('i am a link', function(err, el) { 33 | // call to clickElement will be injected to the queue 34 | // and will be executed sequentially after current function finishes 35 | browser.next('clickElement', el, function() { 36 | console.log("did the click!"); 37 | }); 38 | }) 39 | // ... 40 | ``` 41 | 42 | ### Inserting async code with *queueAddAsync* 43 | 44 | ```javascript 45 | browser 46 | .chain() 47 | // ... 48 | .elementById('i am a link', function(err, el) { 49 | // following call will be executed apart from the current execution chain 50 | // you won't be able to pass results further in chain 51 | // and it may cause racing conditions in your script 52 | }) 53 | .queueAddAsync( function(cb) { 54 | // your code here 55 | cb(null); 56 | }) 57 | .clickElement(el, function() { 58 | console.log("did the click!"); 59 | }) 60 | // ... 61 | ``` 62 | -------------------------------------------------------------------------------- /wd/doc/jsonwire-mobile.md: -------------------------------------------------------------------------------- 1 | ### Mobile JsonWire Protocol Methods ### 2 | 3 | Wd.js is incrementally implementing the Mobile JsonWire Protocol Spec 4 | - read the [draft](https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile) 5 | - Look for `mjsonWire` in the [api doc](https://github.com/admc/wd/blob/master/doc/api.md). 6 | 7 | #### -ios uiautomation Locator Strategy #### 8 | 9 | Find elements in iOS applications using the [UIAutomation Javascript API](https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html) 10 | 11 | eg: 12 | ``` 13 | wd.elementsByIosUIAutomation('.tableViews()[0].cells()', function(err, el){ 14 | el.elementByIosUIAutomation('.elements()["UICatalog"]', function(err, el){ 15 | el.getAttribute('name', function(err, name){ 16 | console.log(name); 17 | }); 18 | }); 19 | }); 20 | ``` 21 | 22 | #### -android uiautomator Locator Strategy #### 23 | 24 | Find elements in android applications using the [UiSelector Class](http://developer.android.com/tools/help/uiautomator/UiSelector.html) 25 | 26 | eg: 27 | ``` 28 | wd.elementsByAndroidUIAutomator('new UiSelector().clickable(true)', function(err, els){ 29 | console.log("number of clickable elements:", els.length); 30 | }); 31 | ``` 32 | 33 | #### accessibility id #### 34 | 35 | Find elements by whatever identifier is used by the platforms Accessibility framework. 36 | 37 | eg: 38 | ``` 39 | wd.elementByAccessibilityId("Submit", function(err, el){ 40 | el.click(); 41 | }); 42 | -------------------------------------------------------------------------------- /wd/doc/jsonwire-unsupported-mapping.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 16 | 19 | 20 | 21 | 25 | 28 | 29 | 30 | 34 | 37 | 38 | 39 | 43 | 46 | 47 | 48 | 52 | 55 | 56 | 57 | 61 | 64 | 65 | 66 | 70 | 73 | 74 | 75 | 79 | 82 | 83 | 84 | 88 | 91 | 92 | 93 | 97 | 100 | 101 | 102 | 106 | 109 | 110 | 111 | 115 | 118 | 119 | 120 | 124 | 127 | 128 | 129 | 133 | 136 | 137 | 138 | 142 | 145 | 146 | 147 | 151 | 154 | 155 | 156 | 159 | 162 | 163 | 164 | 167 | 170 | 171 | 172 | 175 | 178 | 179 | 180 |
5 | category / JsonWireProtocol method 6 | 8 | wd methods 9 |
13 | GET /session/:sessionId/element/:id
14 | Describe the identified element. 15 |
17 | NA 18 |
22 | POST /session/:sessionId/touch/down
23 | Finger down on the screen. 24 |
26 | NA 27 |
31 | POST /session/:sessionId/touch/up
32 | Finger up on the screen. 33 |
35 | NA 36 |
40 | POST /session/:sessionId/touch/move
41 | Finger move on the screen. 42 |
44 | NA 45 |
49 | POST /session/:sessionId/touch/scroll
50 | Scroll on the touch screen using finger based motion events. 51 |
53 | NA 54 |
58 | POST /session/:sessionId/touch/doubleclick
59 | Double tap on the touch screen using finger motion events. 60 |
62 | NA 63 |
67 | POST /session/:sessionId/touch/longclick
68 | Long press on the touch screen using finger motion events. 69 |
71 | NA 72 |
76 | GET /session/:sessionId/local_storage
77 | Get all keys of the storage. 78 |
80 | NA 81 |
85 | GET /session/:sessionId/local_storage/size
86 | Get the number of items in the storage. 87 |
89 | NA 90 |
94 | GET /session/:sessionId/session_storage
95 | Get all keys of the storage. 96 |
98 | NA 99 |
103 | POST /session/:sessionId/session_storage
104 | Set the storage item for the given key. 105 |
107 | NA 108 |
112 | DELETE /session/:sessionId/session_storage
113 | Clear the storage. 114 |
116 | NA 117 |
121 | GET /session/:sessionId/session_storage/key/:key
122 | Get the storage item for the given key. 123 |
125 | NA 126 |
130 | DELETE /session/:sessionId/session_storage/key/:key
131 | Remove the storage item for the given key. 132 |
134 | NA 135 |
139 | GET /session/:sessionId/session_storage/size
140 | Get the number of items in the storage. 141 |
143 | NA 144 |
148 | GET /session/:sessionId/application_cache/status
149 | Get the status of the html5 application cache. 150 |
152 | NA 153 |
157 | MISSING: POST /session/:sessionId/appium/device/is_locked 158 | 160 | isLocked(cb) -> cb(err)
161 |
165 | MISSING: GET /session/:sessionId/appium/settings 166 | 168 | settings(cb) -> cb(err, settingsObject)
169 |
173 | MISSING: POST /session/:sessionId/appium/settings 174 | 176 | updateSettings(settingsObject, cb) -> cb(err)
177 |
181 | -------------------------------------------------------------------------------- /wd/doc/mapping-builder.js: -------------------------------------------------------------------------------- 1 | var mappingType = process.argv[2] || 'supported'; 2 | 3 | var fs = require("fs"), 4 | mu = require('mu2'), 5 | _ = require('lodash'); 6 | 7 | mu.root = __dirname; 8 | 9 | var jsonWireFull = JSON.parse(fs.readFileSync('doc/jsonwire-full.json').toString()); 10 | 11 | var jsonDocs = [ 12 | JSON.parse(fs.readFileSync('tmp/webdriver-dox.json').toString()), 13 | JSON.parse(fs.readFileSync('tmp/element-dox.json').toString()), 14 | JSON.parse(fs.readFileSync('tmp/commands-dox.json').toString()), 15 | JSON.parse(fs.readFileSync('tmp/element-commands-dox.json').toString()), 16 | JSON.parse(fs.readFileSync('tmp/main-dox.json').toString()), 17 | JSON.parse(fs.readFileSync('tmp/asserters-dox.json').toString()) 18 | ]; 19 | 20 | var resMapping = []; 21 | 22 | // main mapping 23 | _(jsonWireFull).each(function (jw_v, jw_k) { 24 | var current = { 25 | jsonWire: { 26 | key: jw_k, 27 | method: jw_k.split(' ')[0], 28 | path: jw_k.split(' ')[1], 29 | url: "http://code.google.com/p/selenium/wiki/JsonWireProtocol#" + jw_k.replace(/\s/g, '_'), 30 | desc: jw_v 31 | }, 32 | wd_doc: [] 33 | }; 34 | _(jsonDocs).each(function (jsonDoc) { 35 | _(jsonDoc).each(function (wd_v) { 36 | if( _(wd_v.tags).filter(function (t) { 37 | return (t.type === 'jsonWire') && (t.string === jw_k); 38 | }).size() > 0){ 39 | var orderTag = _(wd_v.tags).filter(function (t) { 40 | return t.type === 'docOrder'; 41 | }).value(); 42 | var order = 1000000; 43 | if (orderTag.length > 0){ 44 | order = parseInt(orderTag[0].string, 10); 45 | } 46 | var desc = _(wd_v.description.full.split('\n')).filter(function (l) { 47 | return l !== ''; 48 | }).map(function (l) { 49 | return {line: l}; 50 | }).value(); 51 | current.wd_doc.push({ 52 | 'desc': desc, 53 | 'order': order 54 | }); 55 | } 56 | }); 57 | }); 58 | 59 | current.wd_doc = _(current.wd_doc).sortBy(function (docItem) { 60 | return docItem.order; 61 | }).value(); 62 | current.wd_doc0 = current.wd_doc.length === 0; 63 | current.wd_doc1 = current.wd_doc.length === 1? current.wd_doc : null; 64 | current.wd_docN = current.wd_doc.length > 1? current.wd_doc: null; 65 | 66 | if( (mappingType === 'full') || 67 | ((mappingType === 'supported') && (current.wd_doc.length > 0) ) || 68 | ((mappingType === 'unsupported') && (current.wd_doc.length === 0) ) ) { 69 | 70 | resMapping.push(current); 71 | } 72 | }); 73 | 74 | // extra section 75 | _(jsonDocs).each(function (jsonDoc) { 76 | _(jsonDoc).each(function (wd_v) { 77 | if(_(wd_v.tags).filter(function (t) { 78 | return t.type === 'jsonWire' || 79 | t.type === 'asserter' || 80 | t.type === 'wd'; 81 | }).size() === 0){ 82 | var current = { 83 | extra: true, 84 | wd_doc: [] 85 | }; 86 | var desc = _(wd_v.description.full.split('\n')).filter(function (l) { 87 | return l !== ''; 88 | }).map(function (l) { 89 | return {line: l}; 90 | }).value(); 91 | current.wd_doc.push({ 'desc': desc }); 92 | current.wd_doc1 = current.wd_doc; 93 | 94 | if( (mappingType === 'full') || 95 | (mappingType === 'supported') ) { 96 | resMapping.push(current); 97 | } 98 | } 99 | }); 100 | }); 101 | 102 | // asserter section 103 | _(jsonDocs).each(function (jsonDoc) { 104 | _(jsonDoc).each(function (wd_v) { 105 | if(_(wd_v.tags).filter(function (t) { 106 | return t.type === 'asserter'; 107 | }).size() > 0){ 108 | var current = { 109 | asserter: true, 110 | wd_doc: [] 111 | }; 112 | var desc = _(wd_v.description.full.split('\n')).filter(function (l) { 113 | return l !== ''; 114 | }).map(function (l) { 115 | return {line: l}; 116 | }).value(); 117 | current.wd_doc.push({ 'desc': desc }); 118 | current.wd_doc1 = current.wd_doc; 119 | 120 | if( (mappingType === 'full') || 121 | (mappingType === 'supported') ) { 122 | resMapping.push(current); 123 | } 124 | } 125 | }); 126 | }); 127 | 128 | // wd section 129 | _(jsonDocs).each(function (jsonDoc) { 130 | _(jsonDoc).each(function (wd_v) { 131 | if(_(wd_v.tags).filter(function (t) { 132 | return t.type === 'wd'; 133 | }).size() > 0){ 134 | var current = { 135 | wd: true, 136 | wd_doc: [] 137 | }; 138 | var desc = _(wd_v.description.full.split('\n')).filter(function (l) { 139 | return l !== ''; 140 | }).map(function (l) { 141 | return {line: l}; 142 | }).value(); 143 | current.wd_doc.push({ 'desc': desc }); 144 | current.wd_doc1 = current.wd_doc; 145 | 146 | if( (mappingType === 'full') || 147 | (mappingType === 'supported') ) { 148 | resMapping.push(current); 149 | } 150 | } 151 | }); 152 | }); 153 | // missing section, looking for errors 154 | _(jsonDocs).each(function (jsonDoc) { 155 | _(jsonDoc).each(function (wd_v) { 156 | _(_(wd_v.tags).filter(function (t) { 157 | return t.type === 'jsonWire'; 158 | })).each(function (t) { 159 | var tag = t.string; 160 | if(!jsonWireFull[tag]){ 161 | var current = { 162 | missing: { 163 | key:tag 164 | }, 165 | wd_doc: [] 166 | }; 167 | var desc = _(wd_v.description.full.split('\n')).filter(function (l) { 168 | return l !== ''; 169 | }).map(function (l) { 170 | return {line: l}; 171 | }).value(); 172 | current.wd_doc.push({desc: desc}); 173 | current.wd_doc1 = current.wd_doc; 174 | resMapping.push(current); 175 | } 176 | }); 177 | }); 178 | }); 179 | 180 | var output = ''; 181 | mu.compileAndRender( 'mapping-template.htm', {mapping: resMapping}) 182 | .on('data', function (data) { 183 | output += data.toString(); 184 | }) 185 | .on('end', function () { 186 | _(output.split('\n')).each(function (line) { 187 | line = line.trim(); 188 | if(line !== '' ){ 189 | process.stdout.write(line + '\n'); 190 | } 191 | }); 192 | }); 193 | 194 | -------------------------------------------------------------------------------- /wd/doc/mapping-template.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | {{#mapping}} 12 | 13 | 31 | 48 | 49 | {{/mapping}} 50 | 51 |
5 | category / JsonWireProtocol method 6 | 8 | wd methods 9 |
14 | {{#jsonWire}} 15 | {{method}} {{path}}
16 | {{desc}} 17 | {{/jsonWire}} 18 | {{#asserter}} 19 | asserter 20 | {{/asserter}} 21 | {{#wd}} 22 | wd 23 | {{/wd}} 24 | {{#extra}} 25 | extra 26 | {{/extra}} 27 | {{#missing}} 28 | MISSING: {{key}} 29 | {{/missing}} 30 |
32 | {{#wd_doc0}} 33 | NA 34 | {{/wd_doc0}} 35 | {{#wd_doc1}} 36 | {{#desc}} 37 | {{line}}
38 | {{/desc}} 39 | {{/wd_doc1}} 40 | {{#wd_docN}} 41 |

42 | {{#desc}} 43 | {{line}}
44 | {{/desc}} 45 |

46 | {{/wd_docN}} 47 |
52 | -------------------------------------------------------------------------------- /wd/doc/release-notes.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | ## 0.3.x Release 4 | 5 | ### 0.3.12 6 | - geolocation fix 7 | - package update 8 | - lodash upgrade 9 | - doc fixes 10 | 11 | ### 0.3.11 12 | - http retry on EPIPE 13 | 14 | ### 0.3.10 15 | - package upgrade 16 | - wd-no-defaults cap 17 | 18 | ### 0.3.9 19 | - package upgrade 20 | 21 | ### 0.3.8 22 | - added unlockDevice method 23 | - chainable tap 24 | 25 | ### 0.3.7 26 | 27 | - added getSettings and updateSettings methods 28 | 29 | ### 0.3.6 30 | 31 | - added startActivity method 32 | 33 | ### 0.3.5 34 | 35 | - mjson element.setText method 36 | 37 | ### 0.3.4 38 | 39 | - error fix 40 | - http timeout fix 41 | 42 | ### 0.3.3 43 | 44 | - new IME mjson methods. 45 | 46 | ### 0.3.2 47 | 48 | - hideKeyboard method update. 49 | 50 | ### 0.3.1 51 | 52 | - new pullFolder method. 53 | - Appium detection fix. 54 | 55 | ### 0.3.0 56 | 57 | - TouchAction/MultiAction update now works like the [W3 specs](https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#multiactions-1). 58 | See the following sample: 59 | 60 | ```js 61 | TouchAction a1 = (new wd.TouchAction(driver)).press({el: el}).release(); 62 | a1.perform(); 63 | TouchAction a2 = (new wd.TouchAction()).press({el: el}).release(); 64 | TouchAction a3 = (new wd.TouchAction()).tap({el: el, x50, y: 50}).wait({ms: 10000}); 65 | MultiAction ma = (new wd.MultiAction(driver)).add(a2, a2); 66 | ma.perform(); 67 | ``` 68 | 69 | ## 0.2.x Release 70 | 71 | ### 0.2.27 72 | - http proxy fix. 73 | 74 | ### 0.2.26 75 | - openNotifications + getAppStrings fix. 76 | 77 | ### 0.2.25 78 | - getNetworkConnection fix. 79 | 80 | ### 0.2.24 81 | - add custom methods to element prototypes. 82 | 83 | ### 0.2.23 84 | - appium/selendroid network connection methods. 85 | 86 | ### 0.2.22 87 | - packages upgrade 88 | - http proxy options 89 | - configurable default chaining scope 90 | 91 | ### 0.2.21 92 | - better sauce job update logic 93 | 94 | ### 0.2.20 95 | - configurable sauce rest root 96 | 97 | ### 0.2.19 98 | - packages upgrade 99 | 100 | ### 0.2.18 101 | - bugfixes: getAppString + element unique arguments 102 | - extra Appium method 103 | 104 | ### 0.2.17 105 | - bugfix: TouchAction.moveTo 106 | - stricter jshint 107 | 108 | ### 0.2.16 109 | - more mjson/appium methods 110 | - command argument bugfix 111 | 112 | ### 0.2.15 113 | - extra mobile/appium method 114 | - command arguments bugfix 115 | 116 | ### 0.2.14 117 | - no defaults for appium 118 | - better mobile examples 119 | 120 | ### 0.2.13 121 | - waitForElement fix 122 | - added waitForElements method 123 | - auth fix 124 | - new context methods 125 | 126 | ### 0.2.12 127 | - minor bugfix 128 | 129 | ### 0.2.11 130 | - better logging 131 | - better error handling 132 | 133 | ### 0.2.10 134 | - packages upgrade to latest. 135 | 136 | ### 0.2.9 137 | - http emit fix. 138 | - added print method 139 | - added at, nth, first, second, third, last to promise api 140 | 141 | ### 0.2.8 142 | - added nodeify to transferPromiseness. 143 | 144 | 145 | ### 0.2.7 146 | - `attach`/`detach` session. 147 | - add `asyncRemote` and make `remote` generic. 148 | 149 | ### 0.2.6 150 | 151 | - bugfix: Removed the tmp dependencies. 152 | - isDisplayed/isNotDisplayed asserters 153 | - isVisible depreciation 154 | - bugfix: Removed the tmp dependencies. 155 | - bugfix: Value not defaulted when inititializing with `url.parse`. 156 | - bugfix: url relative now use `url.resolve`. 157 | 158 | ### 0.2.5 159 | 160 | - Webdriver and Element refactoring 161 | - Easier wd customization via `wd.setBaseClasses(Webdriver, Element)` 162 | 163 | ### 0.2.4 164 | 165 | - bugfix: android safeExecute. 166 | - bugfix: passing argument to execute. 167 | - bugfix: setOrientation. 168 | - migrating from string.js to underscore.string. 169 | 170 | ### 0.2.3 171 | 172 | - Http configuration enhancements + base url, see doc [here](https://github.com/admc/wd#http-configuration--base-url). 173 | - `waitFor`, `waitForElement` and asserters replacing existing wait methods. 174 | - `addPromiseChainMethod`/`addPromiseMethod`/`addAsyncMethod`/`removeMethod` replacing monkey patching 175 | (Please refer to the add method section in README). 176 | - Support for external promise libraries. 177 | - New saveScreenshot method. 178 | 179 | ### 0.2.2 180 | 181 | - chai-as-promised v4 compatible. 182 | - Promise wrappers can now be monkey patched directly. 183 | - New saucelabs helpers. 184 | 185 | Incompatibilities: 186 | 187 | - There is a new method to call, `wd.rewrap()`, to propagate async monkey patching to promise. 188 | (see [here](https://github.com/admc/wd/blob/master/examples/promise/monkey.patch-with-async.js#L35) 189 | and the monkey patch section in README) [Note: monkey patching and `rewrap` note recommended from 0.2.3]. 190 | - The chai-as-promised setup has changed in v4, look out for the `transferPromiseness` (Requires chai-as-promised 4.1.0 or greater) 191 | line in the examples. (see [here](https://github.com/admc/wd/blob/master/examples/promise/chrome.js#L15)). 192 | 193 | ### 0.2.1 194 | 195 | - New test suite using the promise chain api. 196 | - `browser.Q` was moved to `wd.Q`. 197 | 198 | ### 0.2.0 199 | 200 | - New wrapper: promise chain. 201 | - Old chain api is deprecated (It is still available, but you will see a depreciation message). 202 | - There are some changes in the way the element and webdriver classes are passed around 203 | which may affect external wrappers. External wrappers should now subclass those 2 classes. 204 | 205 | 206 | ### TODO 207 | - write tests for sauceJobUpdate/sauceJobStatus 208 | - Modify doc generator to cope with commands.js 209 | - Integrate with node-saucelabs + make the sauce rest url configurable 210 | - Add wait for elements 211 | - Implement all the missing methods 212 | - Appium mobile methods 213 | - add a util with most commonly used desired config (selenium+appium) 214 | - jQuery addOn + asserters (including jquery visible/hidden) (todo) 215 | - better remote/init process 216 | -------------------------------------------------------------------------------- /wd/lib/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | const __slice = Array.prototype.slice; 5 | const Webdriver = require('./webdriver'); 6 | const Element = require('./element'); 7 | 8 | /** 9 | * new wd.TouchAction() 10 | * TouchAction constructor 11 | * 12 | * @param driver 13 | * @actions 14 | */ 15 | const TouchAction = function(driver) { 16 | this.driver = driver; 17 | this.gestures = []; 18 | }; 19 | 20 | TouchAction.prototype.addGesture = function(action, opts) { 21 | opts = opts || {}; 22 | const el = opts.element || opts.el; 23 | if (el && !(el instanceof Element)) { 24 | throw new Error('Invalid element or el field passed'); 25 | } 26 | 27 | // preparing opts 28 | const finalOpts = {}; 29 | _(opts).each(function(value, name) { 30 | if (_.isNumber(value)) { 31 | finalOpts[name] = value; 32 | } else if (value instanceof Element) { 33 | finalOpts[name] = value.value; 34 | } else if (value) { 35 | finalOpts[name] = value; 36 | } 37 | }).value(); 38 | if (finalOpts.el) { 39 | finalOpts.element = finalOpts.el; 40 | delete finalOpts.el; 41 | } 42 | 43 | // adding action 44 | this.gestures.push({ 45 | action, 46 | options: finalOpts, 47 | }); 48 | }; 49 | 50 | TouchAction.prototype.toJSON = function() { 51 | return this.gestures; 52 | }; 53 | 54 | /** 55 | * touchAction.longPress({el, x, y}) 56 | * pass el or (x,y) or both 57 | * 58 | * @param opts 59 | * @actions 60 | */ 61 | TouchAction.prototype.longPress = function(opts) { 62 | this.addGesture('longPress', opts); 63 | return this; 64 | }; 65 | 66 | /** 67 | * touchAction.moveTo({el, x, y}) 68 | * pass el or (x,y) or both 69 | * 70 | * @param opts 71 | * @actions 72 | */ 73 | TouchAction.prototype.moveTo = function(opts) { 74 | this.addGesture('moveTo', opts); 75 | return this; 76 | }; 77 | 78 | /** 79 | * touchAction.press({el, x, y}) 80 | * pass el or (x,y) or both 81 | * 82 | * @param opts 83 | * @actions 84 | */ 85 | TouchAction.prototype.press = function(opts) { 86 | this.addGesture('press', opts); 87 | return this; 88 | }; 89 | 90 | /** 91 | * touchAction.release() 92 | * 93 | * @actions 94 | */ 95 | TouchAction.prototype.release = function() { 96 | this.addGesture('release', {}); 97 | return this; 98 | }; 99 | 100 | /** 101 | * touchAction.tap({el, x, y, count}) 102 | * pass el or (x,y) or both 103 | * count is optional 104 | * 105 | * @param opts 106 | * @actions 107 | */ 108 | TouchAction.prototype.tap = function(opts) { 109 | this.addGesture('tap', opts); 110 | return this; 111 | }; 112 | 113 | /** 114 | * touchAction.wait({ms}) 115 | * touchAction.wait(ms) 116 | * ms is optional 117 | * 118 | * @param opts 119 | * @actions 120 | */ 121 | TouchAction.prototype.wait = function(opts) { 122 | if (_.isNumber(opts)) { opts = { ms: opts }; } 123 | this.addGesture('wait', opts); 124 | return this; 125 | }; 126 | 127 | /** 128 | * cancel the action 129 | * 130 | * @actions 131 | */ 132 | TouchAction.prototype.cancel = function() { 133 | this.gestures = []; 134 | }; 135 | 136 | /** 137 | * perform the action 138 | * 139 | * @param cb 140 | * @actions 141 | */ 142 | TouchAction.prototype.perform = function(cb) { 143 | if (typeof cb === 'function') { 144 | this.driver.performTouchAction(this, cb); 145 | } else { 146 | return this.driver.performTouchAction(this); 147 | } 148 | }; 149 | 150 | /** 151 | * new wd.MultiAction() 152 | * MultiAction constructor 153 | * 154 | * @param browserOrElement 155 | * @actions 156 | */ 157 | const MultiAction = function(browserOrElement) { 158 | if (browserOrElement instanceof Element) { 159 | this.element = browserOrElement; 160 | this.browser = this.element.browser; 161 | } else if (browserOrElement instanceof Webdriver) { 162 | this.browser = browserOrElement; 163 | } 164 | this.actions = []; 165 | }; 166 | 167 | MultiAction.prototype.toJSON = function() { 168 | const output = {}; 169 | if (this.element) { output.elementId = this.element.value; } 170 | output.actions = _(this.actions).map(function(action) { 171 | return action.toJSON(); 172 | }).value(); 173 | return output; 174 | }; 175 | 176 | /** 177 | * multiAction.add(touchAction) 178 | * 179 | * @actions 180 | */ 181 | MultiAction.prototype.add = function() { 182 | const actions = __slice.call(arguments, 0); 183 | this.actions = this.actions.concat(actions); 184 | return this; 185 | }; 186 | 187 | /** 188 | * multiAction.cancel() 189 | * 190 | * @actions 191 | */ 192 | MultiAction.prototype.cancel = function() { 193 | this.actions = []; 194 | }; 195 | 196 | /** 197 | * multiAction.perform() 198 | * 199 | * @param cb 200 | * @actions 201 | */ 202 | MultiAction.prototype.perform = function(cb) { 203 | if (typeof cb === 'function') { 204 | if (this.element) { 205 | this.element.performMultiAction(this, cb); 206 | } else { 207 | this.browser.performMultiAction(this, cb); 208 | } 209 | } else { 210 | if (this.element) { 211 | return this.element.performMultiAction(this); 212 | } 213 | return this.browser.performMultiAction(this); 214 | 215 | } 216 | }; 217 | 218 | module.exports = { 219 | TouchAction, 220 | MultiAction, 221 | }; 222 | -------------------------------------------------------------------------------- /wd/lib/asserters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('./lodash'); 4 | const __slice = Array.prototype.slice; 5 | const utils = require('./utils'); 6 | const deprecator = utils.deprecator; 7 | 8 | function Asserter(_assert) { 9 | this.assert = _assert; 10 | } 11 | 12 | /** 13 | * asserters.nonEmptyText 14 | * 15 | * @asserter 16 | */ 17 | const nonEmptyText = new Asserter( 18 | function(target, cb) { 19 | target.text(function(err, text) { 20 | if (err) { return cb(err); } 21 | const satisfied = text && _(text).trim().value().length > 0; 22 | cb(null, satisfied, satisfied ? text : undefined); 23 | }); 24 | } 25 | ); 26 | 27 | /** 28 | * asserters.textInclude(content) -> Asserter 29 | * 30 | * @param content 31 | * @asserter 32 | */ 33 | function textInclude(content) { 34 | return new Asserter( 35 | function(target, cb) { 36 | target.text(function(err, text) { 37 | if (err) { return cb(err); } 38 | const satisfied = text && _(text).includeString(content).value(); 39 | cb(null, satisfied, satisfied ? text : undefined); 40 | }); 41 | } 42 | ); 43 | } 44 | 45 | /** 46 | * asserters.isVisible 47 | * 48 | * @asserter 49 | */ 50 | const isDisplayed = new Asserter( 51 | function(el, cb) { 52 | el.isDisplayed(function(err, displayed) { 53 | if (err) { return cb(err); } 54 | cb(null, displayed); 55 | }); 56 | } 57 | ); 58 | const isVisible = new Asserter( 59 | function() { 60 | deprecator.warn('isVisible asserter', 'isVisible asserter has been deprecated, use isDisplayed asserter instead.'); 61 | const args = __slice.call(arguments, 0); 62 | isDisplayed.assert.apply(this, args); 63 | } 64 | ); 65 | 66 | /** 67 | * asserters.isHidden 68 | * 69 | * @asserter 70 | */ 71 | const isNotDisplayed = new Asserter( 72 | function(el, cb) { 73 | el.isDisplayed(function(err, displayed) { 74 | if (err) { return cb(err); } 75 | cb(null, !displayed); 76 | }); 77 | } 78 | ); 79 | const isHidden = new Asserter( 80 | function() { 81 | deprecator.warn('isHidden asserter', 'isHidden asserter has been deprecated, use isNotDisplayed asserter instead.'); 82 | const args = __slice.call(arguments, 0); 83 | isNotDisplayed.assert.apply(this, args); 84 | } 85 | ); 86 | 87 | /** 88 | * asserters.jsCondition(jsConditionExpr) -> Asserter 89 | * jsConditionExpr: js script expression, should evaluate as boolean. 90 | * 91 | * @param jsConditionExpr 92 | * @param safe 93 | * @asserter 94 | */ 95 | function jsCondition(jsConditionExpr, safe) { 96 | // jshint evil: true 97 | if (safe === undefined) { safe = false; } 98 | return new Asserter( 99 | function(browser, cb) { 100 | const _eval = safe ? browser.safeEval : browser.eval; 101 | _eval.apply(browser, [ jsConditionExpr, function(err, res) { 102 | if (err) { return cb(err); } 103 | cb(null, res, res); 104 | } ]); 105 | } 106 | ); 107 | } 108 | 109 | module.exports = { 110 | Asserter, 111 | nonEmptyText, 112 | isDisplayed, 113 | isNotDisplayed, 114 | textInclude, 115 | jsCondition, 116 | // deprecated 117 | isVisible, 118 | isHidden, 119 | }; 120 | -------------------------------------------------------------------------------- /wd/lib/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const net = require('net'); 6 | const repl = require('repl'); 7 | const assert = require('assert'); 8 | const wd = require('./main'); 9 | 10 | const startRepl = function() { 11 | const r = repl.start('(wd): '); 12 | r.context.assert = assert; 13 | r.context.wd = wd; 14 | r.context.help = function() { 15 | console.log('WD - Shell.'); 16 | console.log("Access the webdriver object via the object: 'wd'"); 17 | }; 18 | 19 | const server = net.createServer(function(socket) { 20 | socket.setTimeout(5 * 60 * 1000, function() { 21 | socket.destroy(); 22 | }); 23 | repl.start('(wd): ', socket); 24 | }).listen(process.platform === 'win32' ? 25 | '\\\\.\\pipe\\node-repl-sock-' + process.pid : 26 | '/tmp/node-repl-sock-' + process.pid); 27 | 28 | r.on('exit', function() { 29 | server.close(); 30 | process.exit(); 31 | }); 32 | }; 33 | 34 | if (process.argv[2] === 'shell') { 35 | startRepl(); 36 | } 37 | -------------------------------------------------------------------------------- /wd/lib/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('./utils'); 4 | const newError = utils.newError; 5 | const getJsonwireError = utils.getJsonwireError; 6 | const isWebDriverException = utils.isWebDriverException; 7 | 8 | const cbStub = function() { 9 | // none 10 | }; 11 | 12 | // just calls the callback when there is no result 13 | exports.simpleCallback = function(cb) { 14 | cb = cb || cbStub; 15 | return function(err, data) { 16 | if (err) { return cb(err); } 17 | if ((data === '') || (data === 'OK')) { 18 | // expected behaviour when not returning JsonWire response 19 | cb(null); 20 | } else { 21 | // looking for JsonWire response 22 | let jsonWireRes; 23 | try { 24 | jsonWireRes = JSON.parse(data); 25 | } catch (ign) { 26 | // none 27 | } 28 | if (jsonWireRes && (jsonWireRes.status !== undefined)) { 29 | // valid JsonWire response 30 | if (jsonWireRes.status === 0) { 31 | cb(null); 32 | } else { 33 | const jsonwireError = getJsonwireError(jsonWireRes.status); 34 | let errorMessage = 'Error response status: ' + jsonWireRes.status; 35 | if (jsonwireError) { 36 | errorMessage += ', ' + jsonwireError.summary + ' - ' + jsonwireError.detail; 37 | } 38 | if (jsonWireRes.value && jsonWireRes.value.message) { 39 | errorMessage += ' Selenium error: ' + jsonWireRes.value.message; 40 | } 41 | const error = newError( 42 | { message: errorMessage, 43 | status: jsonWireRes.status, 44 | cause: jsonWireRes }); 45 | if (jsonwireError) { error['jsonwire-error'] = jsonwireError; } 46 | cb(error); 47 | } 48 | } else { 49 | // something wrong 50 | cb(newError( 51 | { message: 'Unexpected data in simpleCallback.', data: jsonWireRes || data })); 52 | } 53 | } 54 | }; 55 | }; 56 | 57 | // base for all callback handling data 58 | const callbackWithDataBase = function(cb) { 59 | cb = cb || cbStub; 60 | return function(err, data) { 61 | if (err) { return cb(err); } 62 | let obj, 63 | alertText; 64 | try { 65 | obj = JSON.parse(data); 66 | } catch (e) { 67 | return cb(newError({ message: 'Not JSON response', data })); 68 | } 69 | try { 70 | alertText = obj.value.alert.text; 71 | } catch (e) { 72 | alertText = ''; 73 | } 74 | if (obj.status > 0) { 75 | const jsonwireError = getJsonwireError(obj.status); 76 | let errorMessage = 'Error response status: ' + obj.status + ', ' + alertText; 77 | if (jsonwireError) { 78 | errorMessage += ', ' + jsonwireError.summary + ' - ' + jsonwireError.detail; 79 | } 80 | if (obj.value && obj.value.message) { 81 | errorMessage += ' Selenium error: ' + obj.value.message; 82 | } 83 | const error = newError( 84 | { message: errorMessage, 85 | status: obj.status, 86 | cause: obj }); 87 | if (jsonwireError) { error['jsonwire-error'] = jsonwireError; } 88 | cb(error); 89 | } else { 90 | cb(null, obj); 91 | } 92 | }; 93 | }; 94 | 95 | // retrieves field value from result 96 | exports.callbackWithData = function(cb, browser) { 97 | cb = cb || cbStub; 98 | return callbackWithDataBase(function(err, obj) { 99 | if (err) { return cb(err); } 100 | if (isWebDriverException(obj.value)) { 101 | return cb(newError( 102 | { message: obj.value.message, cause: obj.value })); 103 | } 104 | // we might get a WebElement back as part of executeScript, so let's 105 | // check to make sure we convert if necessary to element objects 106 | if (obj.value && typeof obj.value.ELEMENT !== 'undefined') { 107 | obj.value = browser.newElement(obj.value.ELEMENT); 108 | } else if (Object.prototype.toString.call(obj.value) === '[object Array]') { 109 | for (let i = 0; i < obj.value.length; i++) { 110 | if (obj.value[i] && typeof obj.value[i].ELEMENT !== 'undefined') { 111 | obj.value[i] = browser.newElement(obj.value[i].ELEMENT); 112 | } 113 | } 114 | } 115 | cb(null, obj.value); 116 | }); 117 | }; 118 | 119 | // retrieves ONE element 120 | exports.elementCallback = function(cb, browser) { 121 | cb = cb || cbStub; 122 | return callbackWithDataBase(function(err, obj) { 123 | if (err) { return cb(err); } 124 | if (isWebDriverException(obj.value)) { 125 | return cb(newError( 126 | { message: obj.value.message, cause: obj.value })); 127 | } 128 | if (!obj.value.ELEMENT) { 129 | cb(newError( 130 | { message: 'no ELEMENT in response value field.', cause: obj })); 131 | } else { 132 | const el = browser.newElement(obj.value.ELEMENT); 133 | cb(null, el); 134 | } 135 | }); 136 | }; 137 | 138 | // retrieves SEVERAL elements 139 | exports.elementsCallback = function(cb, browser) { 140 | cb = cb || cbStub; 141 | return callbackWithDataBase(function(err, obj) { 142 | if (err) { return cb(err); } 143 | if (isWebDriverException(obj.value)) { 144 | return cb(newError( 145 | { message: obj.value.message, cause: obj.value })); 146 | } 147 | if (!(obj.value instanceof Array)) { 148 | return cb(newError( 149 | { message: 'Response value field is not an Array.', cause: obj.value })); 150 | } 151 | let i; 152 | const elements = []; 153 | for (i = 0; i < obj.value.length; i++) { 154 | const el = browser.newElement(obj.value[i].ELEMENT); 155 | elements.push(el); 156 | } 157 | cb(null, elements); 158 | }); 159 | }; 160 | 161 | // retrieves ONE or SEVERAL elements 162 | exports.elementOrElementsCallback = function(cb, browser) { 163 | cb = cb || cbStub; 164 | return callbackWithDataBase(function(err, obj) { 165 | if (err) { return cb(err); } 166 | if (isWebDriverException(obj.value)) { 167 | return cb(newError( 168 | { message: obj.value.message, cause: obj.value })); 169 | } 170 | let el; 171 | if (obj.value.ELEMENT) { 172 | el = browser.newElement(obj.value.ELEMENT); 173 | cb(null, el); 174 | } else if (obj.value instanceof Array) { 175 | let i; 176 | const elements = []; 177 | for (i = 0; i < obj.value.length; i++) { 178 | el = browser.newElement(obj.value[i].ELEMENT); 179 | elements.push(el); 180 | } 181 | cb(null, elements); 182 | } else { 183 | cb(newError( 184 | { message: 'no element or element array in response value field.', cause: obj })); 185 | } 186 | }); 187 | }; 188 | -------------------------------------------------------------------------------- /wd/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('./lodash'); 4 | 5 | const httpConfig = { 6 | timeout: undefined, 7 | retries: 3, 8 | retryDelay: 15, 9 | baseUrl: undefined, 10 | proxy: undefined, 11 | }; 12 | 13 | function _configureHttp(httpConfig, opts) { 14 | _(_.keys(httpConfig)).intersection(_.keys(opts)).each(function(key) { 15 | switch (key) { 16 | case 'timeout': 17 | if (opts[key] === 'default') { opts[key] = undefined; } 18 | break; 19 | case 'retries': 20 | if (opts[key] === 'always') { opts[key] = 0; } 21 | if (opts[key] === 'never') { opts[key] = -1; } 22 | break; 23 | default: 24 | break; 25 | } 26 | httpConfig[key] = opts[key]; 27 | }, this) 28 | .value(); 29 | } 30 | 31 | function configureHttp(opts) { 32 | _configureHttp(httpConfig, opts); 33 | } 34 | 35 | module.exports = { 36 | httpConfig, 37 | _configureHttp, 38 | configureHttp, 39 | }; 40 | -------------------------------------------------------------------------------- /wd/lib/deprecated-chain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require('async'); 4 | const _ = require('./lodash'); 5 | 6 | const deprecatedChain = {}; 7 | 8 | deprecatedChain.chain = function(obj) { 9 | const _this = this; 10 | if (!obj) { obj = {}; } 11 | 12 | // Update the onError callback if supplied. The most recent .chain() 13 | // invocation overrides previous onError handlers. 14 | if (obj.onError) { 15 | this._chainOnErrorCallback = obj.onError; 16 | } else if (!this._chainOnErrorCallback) { 17 | this._chainOnErrorCallback = function(err) { 18 | if (err) { console.error('a function in your .chain() failed:', err); } 19 | }; 20 | } 21 | 22 | // Add queue if not already here 23 | if (!_this._queue) { 24 | _this._queue = async.queue(function(task, callback) { 25 | if (task.args.length > 0 && typeof task.args[task.args.length - 1] === 'function') { 26 | // wrap the existing callback 27 | // if this is queueAddAsync, we instead create a callback that will be 28 | // passed through to the function provided 29 | const cb_arg = (task.name === 'queueAddAsync' ? 1 : task.args.length - 1); 30 | const func = task.args[cb_arg]; 31 | task.args[cb_arg] = function(err) { 32 | // if the chain user has their own callback, we will not invoke 33 | // the onError handler, supplying your own callback suggests you 34 | // handle the error on your own. 35 | if (func) { func.apply(null, arguments); } 36 | if (!_this._chainHalted) { callback(err); } 37 | }; 38 | } else { 39 | // if the .chain() does not supply a callback, we assume they 40 | // expect us to catch errors. 41 | task.args.push(function(err) { 42 | // if there is an error, call the onError callback, 43 | // and do not invoke callback() which would make the 44 | // task queue continue processing 45 | if (err) { _this._chainOnErrorCallback(err); } else { callback(); } 46 | }); 47 | } 48 | 49 | // call the function 50 | _this[task.name].apply(_this, task.args); 51 | }, 1); 52 | 53 | // add unshift method if we need to add sth to the queue 54 | _this._queue = _.extend(_this._queue, { 55 | unshift(data, callback) { 56 | const _this = this; 57 | if (data.constructor !== Array) { 58 | data = [ data ]; 59 | } 60 | data.forEach(function(task) { 61 | _this.tasks.unshift({ 62 | data: task, 63 | callback: typeof callback === 'function' ? callback : null, 64 | }); 65 | if (_this.saturated && _this.tasks.length === _this.concurrency) { 66 | _this.saturated(); 67 | } 68 | async.nextTick(_this.process); 69 | }); 70 | }, 71 | }); 72 | } 73 | 74 | const chain = {}; 75 | 76 | // builds a placeHolder functions 77 | const buildPlaceholder = function(name) { 78 | return function() { 79 | _this._queue.push({ name, args: Array.prototype.slice.call(arguments, 0) }); 80 | return chain; 81 | }; 82 | }; 83 | 84 | // fill the chain with placeholders 85 | _.each(_.functions(_this), function(k) { 86 | if (k !== 'chain') { 87 | chain[k] = buildPlaceholder(k); 88 | } 89 | }); 90 | 91 | return chain; 92 | }; 93 | 94 | // manually stop processing of queued chained functions 95 | deprecatedChain.haltChain = function() { 96 | this._chainHalted = true; 97 | this._queue = null; 98 | }; 99 | 100 | deprecatedChain.pauseChain = function(timeoutMs, cb) { 101 | setTimeout(function() { 102 | cb(); 103 | }, timeoutMs); 104 | return this.chain; 105 | }; 106 | 107 | deprecatedChain.next = function() { 108 | this._queue.unshift({ name: arguments[0], args: _.rest(arguments) }); 109 | }; 110 | 111 | deprecatedChain.queueAdd = function(func) { 112 | func(); 113 | return this.chain; 114 | }; 115 | 116 | deprecatedChain.queueAddAsync = function(func, cb) { 117 | func(cb); 118 | return this.chain; 119 | }; 120 | 121 | module.exports = { 122 | patch(browser) { 123 | _(deprecatedChain).methods().each(function(methodName) { 124 | browser[methodName] = deprecatedChain[methodName].bind(browser); 125 | }) 126 | .value(); 127 | }, 128 | }; 129 | -------------------------------------------------------------------------------- /wd/lib/element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Element object 4 | // Wrapper around browser methods 5 | const __slice = Array.prototype.slice; 6 | const _ = require('./lodash'); 7 | const utils = require('./utils.js'); 8 | const niceArgs = utils.niceArgs; 9 | const niceResp = utils.niceResp; 10 | const elementCommands = require('./element-commands'); 11 | 12 | const Element = function(value, browser) { 13 | this.value = value; 14 | this.browser = browser; 15 | 16 | if (!value) { 17 | throw new Error('no value passed to Element constructor'); 18 | } 19 | 20 | if (!browser) { 21 | throw new Error('no browser passed to Element constructor'); 22 | } 23 | }; 24 | 25 | Element.prototype.emit = function() { 26 | this.browser.emit.apply(this.browser, __slice.call(arguments, 0)); 27 | }; 28 | 29 | Element.prototype.toString = function() { 30 | return String(this.value); 31 | }; 32 | 33 | Element.prototype.toJSON = function() { 34 | return { ELEMENT: this.value }; 35 | }; 36 | 37 | _(elementCommands).each(function(fn, name) { 38 | Element.prototype[name] = function() { 39 | const _this = this; 40 | const fargs = utils.varargs(arguments); 41 | this.emit('command', 'CALL', 'element.' + name + niceArgs(fargs.all)); 42 | const cb = function(err) { 43 | if (err) { 44 | err.message = '[element.' + name + niceArgs(fargs.all) + '] ' + err.message; 45 | fargs.callback(err); 46 | } else { 47 | const cbArgs = __slice.call(arguments, 0); 48 | _this.emit('command', 'RESPONSE', 'element.' + name + niceArgs(fargs.all), 49 | niceResp(_.rest(cbArgs))); 50 | fargs.callback.apply(null, cbArgs); 51 | } 52 | }; 53 | const args = fargs.all.concat([ cb ]); 54 | return fn.apply(this, args); 55 | }; 56 | }).value(); 57 | 58 | module.exports = Element; 59 | -------------------------------------------------------------------------------- /wd/lib/http-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const request = require('request'); 4 | const utils = require('./utils'); 5 | const urllib = require('url'); 6 | const packageDotJson = require('../package.json'); 7 | 8 | exports.buildInitUrl = function(baseUrl) { 9 | return utils.resolveUrl(baseUrl, 'session'); 10 | }; 11 | 12 | exports.emit = function(browser, method, url, data) { 13 | if (typeof data === 'object') { 14 | data = JSON.stringify(data); 15 | } 16 | if (typeof url === 'string') { url = urllib.parse(url); } 17 | browser.emit('http', method, 18 | url.path.replace(browser.sessionID, ':sessionID') 19 | .replace(browser.configUrl.pathname, ''), data 20 | ); 21 | }; 22 | 23 | exports.buildJsonCallUrl = function(baseUrl, sessionID, relPath, absPath) { 24 | let path = [ 'session' ]; 25 | if (sessionID) { path.push('/', sessionID); } 26 | if (relPath) { path.push(relPath); } 27 | if (absPath) { path = [ absPath ]; } 28 | path = path.join(''); 29 | 30 | return utils.resolveUrl(baseUrl, path); 31 | }; 32 | 33 | exports.newHttpOpts = function(method, httpConfig) { 34 | // this._httpConfig 35 | const opts = {}; 36 | 37 | opts.method = method; 38 | opts.headers = {}; 39 | 40 | opts.headers.Connection = 'keep-alive'; 41 | opts.forever = true; 42 | opts.headers['User-Agent'] = 'admc/wd/' + packageDotJson.version; 43 | opts.timeout = httpConfig.timeout; 44 | if (httpConfig.proxy) { opts.proxy = httpConfig.proxy; } 45 | // we need to check method here to cater for PUT and DELETE case 46 | if (opts.method === 'GET' || opts.method === 'POST') { 47 | opts.headers.Accept = 'application/json'; 48 | } 49 | 50 | opts.prepareToSend = function(url, data) { 51 | if (typeof data === 'object') { 52 | data = JSON.stringify(data); 53 | } 54 | this.url = url; 55 | if (opts.method === 'POST' || opts.method === 'PUT') { 56 | this.headers['Content-Type'] = 'application/json; charset=UTF-8'; 57 | this.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); 58 | this.body = data; 59 | } 60 | }; 61 | return opts; 62 | }; 63 | 64 | const shouldRetryOn = function(err) { 65 | return err.code === 'ECONNRESET' || 66 | err.code === 'ETIMEDOUT' || 67 | err.code === 'ESOCKETTIMEDOUT' || 68 | err.code === 'EPIPE'; 69 | }; 70 | 71 | const requestWithRetry = function(httpOpts, httpConfig, emit, cb, attempts) { 72 | request(httpOpts, function(err, res, data) { 73 | if (!attempts) { attempts = 1; } 74 | if (httpConfig.retries >= 0 && 75 | (httpConfig.retries === 0 || (attempts - 1) <= httpConfig.retries) && 76 | err && (shouldRetryOn(err))) { 77 | emit('connection', err.code, 'Lost http connection retrying in ' + httpConfig.retryDelay + ' s.', err); 78 | setTimeout(function() { 79 | requestWithRetry(httpOpts, httpConfig, emit, cb, attempts + 1); 80 | }, httpConfig.retryDelay * 1000); 81 | } else { 82 | if (err) { 83 | emit('connection', err.code, 'Unexpected error.', err); 84 | } 85 | cb(err, res, data); 86 | } 87 | }); 88 | }; 89 | exports.requestWithRetry = requestWithRetry; 90 | 91 | const requestWithoutRetry = function(httpOpts, emit, cb) { 92 | request(httpOpts, function(err, res, data) { 93 | if (err) { 94 | emit('connection', err.code, 'Unexpected error.', err); 95 | } 96 | cb(err, res, data); 97 | }); 98 | }; 99 | exports.requestWithoutRetry = requestWithoutRetry; 100 | -------------------------------------------------------------------------------- /wd/lib/jsonwire-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const JSONWIRE_ERRORS = [ 4 | { 5 | status: 0, 6 | summary: 'Success', 7 | detail: 'The command executed successfully.' }, 8 | { 9 | status: 7, 10 | summary: 'NoSuchElement', 11 | detail: 'An element could not be located on the page using the given search parameters.' }, 12 | { 13 | status: 8, 14 | summary: 'NoSuchFrame', 15 | detail: 'A request to switch to a frame could not be satisfied because the frame could not be found.' }, 16 | { 17 | status: 9, 18 | summary: 'UnknownCommand', 19 | detail: 'The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource.' }, 20 | { 21 | status: 10, 22 | summary: 'StaleElementReference', 23 | detail: 'An element command failed because the referenced element is no longer attached to the DOM.' }, 24 | { 25 | status: 11, 26 | summary: 'ElementNotVisible', 27 | detail: 'An element command could not be completed because the element is not visible on the page.' }, 28 | { 29 | status: 12, 30 | summary: 'InvalidElementState', 31 | detail: 'An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element).' }, 32 | { 33 | status: 13, 34 | summary: 'UnknownError', 35 | detail: 'An unknown server-side error occurred while processing the command.' }, 36 | { 37 | status: 15, 38 | summary: 'ElementIsNotSelectable', 39 | detail: 'An attempt was made to select an element that cannot be selected.' }, 40 | { 41 | status: 17, 42 | summary: 'JavaScriptError', 43 | detail: 'An error occurred while executing user supplied JavaScript.' }, 44 | { 45 | status: 19, 46 | summary: 'XPathLookupError', 47 | detail: 'An error occurred while searching for an element by XPath.' }, 48 | { 49 | status: 21, 50 | summary: 'Timeout', 51 | detail: 'An operation did not complete before its timeout expired.' }, 52 | { 53 | status: 23, 54 | summary: 'NoSuchWindow', 55 | detail: 'A request to switch to a different window could not be satisfied because the window could not be found.' }, 56 | { 57 | status: 24, 58 | summary: 'InvalidCookieDomain', 59 | detail: 'An illegal attempt was made to set a cookie under a different domain than the current page.' }, 60 | { 61 | status: 25, 62 | summary: 'UnableToSetCookie', 63 | detail: 'A request to set a cookie\'s value could not be satisfied.' }, 64 | { 65 | status: 26, 66 | summary: 'UnexpectedAlertOpen', 67 | detail: 'A modal dialog was open, blocking this operation' }, 68 | { 69 | status: 27, 70 | summary: 'NoAlertOpenError', 71 | detail: 'An attempt was made to operate on a modal dialog when one was not open.' }, 72 | { 73 | status: 28, 74 | summary: 'ScriptTimeout', 75 | detail: 'A script did not complete before its timeout expired.' }, 76 | { 77 | status: 29, 78 | summary: 'InvalidElementCoordinates', 79 | detail: 'The coordinates provided to an interactions operation are invalid.' }, 80 | { 81 | status: 30, 82 | summary: 'IMENotAvailable', 83 | detail: 'IME was not available.' }, 84 | { 85 | status: 31, 86 | summary: 'IMEEngineActivationFailed', 87 | detail: 'An IME engine could not be started.' }, 88 | { 89 | status: 32, 90 | summary: 'InvalidSelector', 91 | detail: 'Argument was an invalid selector (e.g. XPath/CSS).' }, 92 | ]; 93 | 94 | module.exports = JSONWIRE_ERRORS; 95 | -------------------------------------------------------------------------------- /wd/lib/lodash.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | _.str = require('underscore.string'); 5 | _.mixin(_.str.exports()); 6 | 7 | _.mixin({ 8 | includeString: _.str.include, 9 | reverseString: _.str.reverse, 10 | }); 11 | 12 | module.exports = _; 13 | -------------------------------------------------------------------------------- /wd/lib/tmp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // inspired by https://github.com/raszi/node-tmp, but only 4 | // provides tmp paths. 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const os = require('os'); 9 | const utils = require('./utils'); 10 | 11 | function _isUndefined(obj) { 12 | return typeof obj === 'undefined'; 13 | } 14 | 15 | function _parseArguments() { 16 | const fargs = utils.varargs(arguments); 17 | const callback = fargs.callback; 18 | const options = fargs.all[0]; 19 | return [ options, callback ]; 20 | } 21 | 22 | /** 23 | * Gets the temp directory. 24 | * 25 | * @return {String} 26 | * @api private 27 | */ 28 | function _getTMPDir() { 29 | const tmpNames = [ 'TMPDIR', 'TMP', 'TEMP' ]; 30 | 31 | for (let i = 0, length = tmpNames.length; i < length; i++) { 32 | if (_isUndefined(process.env[tmpNames[i]])) { continue; } 33 | 34 | return process.env[tmpNames[i]]; 35 | } 36 | 37 | // fallback to the default 38 | return '/tmp'; 39 | } 40 | 41 | const exists = fs.exists || path.exists; 42 | const tmpDir = os.tmpdir || _getTMPDir; 43 | const _TMP = tmpDir(); 44 | const randomChars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'; 45 | const randomCharsLength = randomChars.length; 46 | 47 | /** 48 | * Gets a temporary file name. 49 | * 50 | * @api private 51 | * @param options 52 | * @param callback 53 | */ 54 | function _getTmpName(options, callback) { 55 | const args = _parseArguments(options, callback); 56 | const opts = args[0]; 57 | const cb = args[1]; 58 | const template = opts.template; 59 | const templateDefined = !_isUndefined(template); 60 | let tries = opts.tries || 3; 61 | 62 | if (isNaN(tries) || tries < 0) { return cb(new Error('Invalid tries')); } 63 | 64 | if (templateDefined && !template.match(/XXXXXX/)) { return cb(new Error('Invalid template provided')); } 65 | 66 | function _getName() { 67 | 68 | // prefix and postfix 69 | if (!templateDefined) { 70 | const name = [ 71 | (_isUndefined(opts.prefix)) ? 'tmp-' : opts.prefix, 72 | process.pid, 73 | (Math.random() * 0x1000000000).toString(36), 74 | opts.postfix, 75 | ].join(''); 76 | 77 | return path.join(opts.dir || _TMP, name); 78 | } 79 | 80 | // mkstemps like template 81 | const chars = []; 82 | 83 | for (let i = 0; i < 6; i++) { 84 | chars.push(randomChars.substr(Math.floor(Math.random() * randomCharsLength), 1)); 85 | } 86 | 87 | return template.replace(/XXXXXX/, chars.join('')); 88 | } 89 | 90 | (function _getUniqueName() { 91 | const name = _getName(); 92 | 93 | // check whether the path exists then retry if needed 94 | exists(name, function _pathExists(pathExists) { 95 | if (pathExists) { 96 | if (tries-- > 0) { return _getUniqueName(); } 97 | 98 | return cb(new Error('Could not get a unique tmp filename, max tries reached')); 99 | } 100 | 101 | cb(null, name); 102 | }); 103 | }()); 104 | } 105 | 106 | // exporting all the needed methods 107 | module.exports.tmpdir = _TMP; 108 | module.exports.tmpName = _getTmpName; 109 | -------------------------------------------------------------------------------- /wd/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Args = require('vargs').Constructor; 4 | const _ = require('./lodash'); 5 | const url = require('url'); 6 | const JSONWIRE_ERRORS = require('./jsonwire-errors.js'); 7 | 8 | const varargs = exports.varargs = function(args) { 9 | const fargs = new (Args)(args); 10 | // returning undefined instead of empty callback 11 | fargs.callback = fargs.callbackGiven() ? fargs.callback : undefined; 12 | return fargs; 13 | }; 14 | 15 | // small helper to make sure we don't loose exceptions 16 | // use this instead of looking the last argument manually 17 | exports.findCallback = function(_arguments) { 18 | const fargs = varargs(_arguments); 19 | return fargs.callback; 20 | }; 21 | 22 | // convert to type to something like ById, ByCssSelector, etc... 23 | const STRAT_MAPPING = { 24 | '-ios uiautomation': 'ByIosUIAutomation', 25 | '-android uiautomator': 'ByAndroidUIAutomator', 26 | }; 27 | exports.elFuncSuffix = function(type) { 28 | let suffix = STRAT_MAPPING[type]; 29 | if (!suffix) { 30 | suffix = (' by ' + type).replace(/(\s[a-z])/g, 31 | function($1) { return $1.toUpperCase().replace(' ', ''); }) 32 | .replace('Xpath', 'XPath'); 33 | } 34 | return suffix; 35 | }; 36 | 37 | // return correct jsonwire type 38 | exports.elFuncFullType = function(type) { 39 | if (type === 'css') { return 'css selector'; } // shortcut for css 40 | return type; 41 | }; 42 | 43 | // from JsonWire spec + shortcuts + mobile JsonWire spec 44 | exports.elementFuncTypes = [ 'class name', 'css selector', 'id', 'name', 'link text', 45 | 'partial link text', 'tag name', 'xpath', 'css', '-ios uiautomation', '-android uiautomator', 'accessibility id' ]; 46 | 47 | // chai-as-promised promisifier 48 | // just adding the core method for the sake of safety.\ 49 | // if you need more than that, build your custom promisifier 50 | const Q_CORE_METHODS = [ 51 | // core methods: 52 | 'then', 'catch', 'fail', 'progress', 'finally', 'fin', 'done', 53 | 'thenResolve', 'thenReject', 'nodeify', 54 | ]; 55 | 56 | exports.transferPromiseness = function(target, promise) { 57 | _(Q_CORE_METHODS).each(function(methodName) { 58 | if (promise[methodName]) { 59 | target[methodName] = promise[methodName].bind(promise); 60 | } 61 | }).value(); 62 | if (promise._enrich) { 63 | promise._enrich(target); 64 | } 65 | }; 66 | 67 | // promise detection 68 | exports.isPromise = function(x) { 69 | return (typeof x === 'object' || typeof x === 'function') && x !== null && typeof x.then === 'function'; 70 | }; 71 | 72 | exports.deprecator = { 73 | deprecationMessageShown: {}, 74 | warnDeprecated: true, 75 | showHideDeprecation(status) { 76 | if (status !== undefined) { this.warnDeprecated = status; } else { this.warnDeprecated = !this.warnDeprecated; } 77 | }, 78 | warn(cat, message) { 79 | if (this.warnDeprecated && !this.deprecationMessageShown[cat]) { 80 | this.deprecationMessageShown[cat] = 1; 81 | console.warn(message); 82 | } 83 | }, 84 | }; 85 | 86 | // Android doesn't like cariage return 87 | exports.inlineJs = function(script) { 88 | return script.replace(/[\r\n]/g, '').trim(); 89 | }; 90 | 91 | exports.resolveUrl = function(from, to) { 92 | if (typeof from === 'object') { from = url.format(from); } 93 | 94 | // making sure the last part of the path doesn't get stripped 95 | if (!from.match(/\/$/)) { from += '/'; } 96 | 97 | return url.parse(url.resolve(from, to)); 98 | }; 99 | 100 | exports.strip = function strip(str) { 101 | if (typeof (str) !== 'string') { return str; } 102 | const x = []; 103 | _(str.length).times(function(i) { 104 | if (str.charCodeAt(i)) { 105 | x.push(str.charAt(i)); 106 | } 107 | }).value(); 108 | return x.join(''); 109 | }; 110 | 111 | const trimToLength = function(str, length) { 112 | return (str && str.length > length) ? 113 | str.substring(0, length) + '...' : str; 114 | }; 115 | exports.trimToLength = trimToLength; 116 | 117 | exports.niceArgs = function(args) { 118 | return JSON.stringify(args) 119 | .replace(/^\[/, '(') 120 | .replace(/\]$/, ')'); 121 | }; 122 | 123 | exports.niceResp = function(args) { 124 | return JSON.stringify(args) 125 | .replace(/^\[/, '') 126 | .replace(/\]$/, ''); 127 | }; 128 | 129 | // convert code to string before execution 130 | exports.codeToString = function(code) { 131 | if (typeof code === 'function') { 132 | code = 'return (' + code + ').apply(null, arguments);'; 133 | } 134 | return code; 135 | }; 136 | 137 | const MAX_ERROR_LENGTH = 500; 138 | exports.newError = function(opts) { 139 | const err = new Error(); 140 | _.each(opts, function(opt, k) { 141 | err[k] = opt; 142 | }); 143 | // nicer error output 144 | err.inspect = function() { 145 | const jsonStr = JSON.stringify(err); 146 | return trimToLength(jsonStr, MAX_ERROR_LENGTH); 147 | }; 148 | return err; 149 | }; 150 | 151 | exports.isWebDriverException = function(res) { 152 | return res && 153 | res.class && 154 | (res.class.indexOf('WebDriverException') > 0); 155 | }; 156 | 157 | exports.getJsonwireError = function(status) { 158 | const jsonwireError = JSONWIRE_ERRORS.filter(function(err) { 159 | return err.status === status; 160 | }); 161 | return ((jsonwireError.length > 0) ? jsonwireError[0] : null); 162 | }; 163 | -------------------------------------------------------------------------------- /wd/lib/webdriver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventEmitter = require('events').EventEmitter; 4 | const _ = require('./lodash'); 5 | const util = require('util'); 6 | const url = require('url'); 7 | const __slice = Array.prototype.slice; 8 | const utils = require('./utils'); 9 | const findCallback = utils.findCallback; 10 | const niceArgs = utils.niceArgs; 11 | const niceResp = utils.niceResp; 12 | const strip = utils.strip; 13 | const deprecator = utils.deprecator; 14 | const httpUtils = require('./http-utils'); 15 | const config = require('./config'); 16 | const Element = require('./element'); 17 | const commands = require('./commands'); 18 | 19 | // Webdriver client main class 20 | // configUrl: url object constructed via url.parse 21 | const Webdriver = module.exports = function(configUrl) { 22 | EventEmitter.call(this); 23 | this.sessionID = null; 24 | this.configUrl = configUrl; 25 | // config url without auth 26 | this.noAuthConfigUrl = url.parse(url.format(this.configUrl)); 27 | delete this.noAuthConfigUrl.auth; 28 | 29 | this.defaultCapabilities = { 30 | browserName: 'firefox', 31 | version: '', 32 | javascriptEnabled: true, 33 | platform: 'ANY', 34 | }; 35 | 36 | this._httpConfig = _.clone(config.httpConfig); 37 | }; 38 | 39 | // inherit from EventEmitter 40 | util.inherits(Webdriver, EventEmitter); 41 | 42 | // creates a new element 43 | Webdriver.prototype.newElement = function(jsonWireElement) { 44 | return new Element(jsonWireElement, this); 45 | }; 46 | 47 | /** 48 | * attach(sessionID, cb) -> cb(err) 49 | * Connect to an already-active session. 50 | * @param sessionID 51 | */ 52 | Webdriver.prototype.attach = function(sessionID) { 53 | const cb = findCallback(arguments); 54 | this.sessionID = sessionID; 55 | if (cb) { cb(null); } 56 | }; 57 | 58 | /** 59 | * detach(cb) -> cb(err) 60 | * Detach from the current session. 61 | */ 62 | Webdriver.prototype.detach = function() { 63 | const cb = findCallback(arguments); 64 | this.sessionID = null; 65 | if (cb) { cb(null); } 66 | }; 67 | 68 | commands.chain = function(obj) { 69 | deprecator.warn('chain', 'chain api has been deprecated, use promise chain instead.'); 70 | require('./deprecated-chain').patch(this); 71 | return this.chain(obj); 72 | }; 73 | 74 | Webdriver.prototype._init = function() { 75 | delete this.sessionID; 76 | const _this = this; 77 | const fargs = utils.varargs(arguments); 78 | const cb = fargs.callback; 79 | const desired = fargs.all[0] || {}; 80 | 81 | const _desired = _.clone(desired); 82 | 83 | if (desired.deviceName || desired.device || desired.wdNoDefaults || 84 | desired['wd-no-defaults']) { 85 | // no default or appium caps, we dont default 86 | delete _desired.wdNoDefaults; 87 | delete _desired['wd-no-defaults']; 88 | } else { 89 | // using default 90 | _.defaults(_desired, this.defaultCapabilities); 91 | } 92 | 93 | // http options 94 | const httpOpts = httpUtils.newHttpOpts('POST', _this._httpConfig); 95 | 96 | const url = httpUtils.buildInitUrl(this.configUrl); 97 | 98 | // building request 99 | const data = { desiredCapabilities: _desired }; 100 | 101 | httpUtils.emit(this, httpOpts.method, url, data); 102 | 103 | httpOpts.prepareToSend(url, data); 104 | 105 | httpUtils.requestWithRetry(httpOpts, this._httpConfig, this.emit.bind(this), function(err, res, data) { 106 | if (err) { return cb(err); } 107 | 108 | let resData; 109 | // retrieving session 110 | try { 111 | const jsonData = JSON.parse(data); 112 | if (jsonData.status === 0) { 113 | _this.sessionID = jsonData.sessionId; 114 | resData = jsonData.value; 115 | } 116 | } catch (ignore) { 117 | // none 118 | } 119 | if (!_this.sessionID) { 120 | // attempting to retrieve the session the old way 121 | try { 122 | const locationArr = res.headers.location.replace(/\/$/, '').split('/'); 123 | _this.sessionID = locationArr[locationArr.length - 1]; 124 | } catch (ignore) { 125 | // none 126 | } 127 | } 128 | 129 | if (_this.sessionID) { 130 | _this.emit('status', '\nDriving the web on session: ' + _this.sessionID + '\n'); 131 | if (cb) { cb(null, _this.sessionID, resData); } 132 | } else { 133 | data = strip(data); 134 | if (cb) { 135 | err = new Error('The environment you requested was unavailable.'); 136 | err.data = data; 137 | return cb(err); 138 | } 139 | console.error('\x1b[31mError\x1b[0m: The environment you requested was unavailable.\n'); 140 | console.error('\x1b[33mReason\x1b[0m:\n'); 141 | console.error(data); 142 | console.error('\nFor the available values please consult the WebDriver JSONWireProtocol,'); 143 | console.error('located at: \x1b[33mhttp://code.google.com/p/selenium/wiki/JsonWireProtocol#/session\x1b[0m'); 144 | 145 | } 146 | }); 147 | }; 148 | 149 | // standard jsonwire call 150 | Webdriver.prototype._jsonWireCall = function(opts) { 151 | const _this = this; 152 | 153 | // http options init 154 | const httpOpts = httpUtils.newHttpOpts(opts.method, this._httpConfig); 155 | 156 | const url = httpUtils.buildJsonCallUrl(this.noAuthConfigUrl, this.sessionID, opts.relPath, opts.absPath); 157 | 158 | // building callback 159 | let cb = opts.cb; 160 | if (opts.emit) { 161 | // wrapping cb if we need to emit a message 162 | const _cb = cb; 163 | cb = function() { 164 | const args = __slice.call(arguments, 0); 165 | _this.emit(opts.emit.event, opts.emit.message); 166 | if (_cb) { _cb.apply(_this, args); } 167 | }; 168 | } 169 | 170 | // logging 171 | httpUtils.emit(this, httpOpts.method, url, opts.data); 172 | 173 | // writing data 174 | const data = opts.data || {}; 175 | httpOpts.prepareToSend(url, data); 176 | // building request 177 | httpUtils.requestWithRetry(httpOpts, this._httpConfig, this.emit.bind(this), function(err, res, data) { 178 | if (err) { return cb(err); } 179 | data = strip(data); 180 | cb(null, data || ''); 181 | }); 182 | }; 183 | 184 | _(commands).each(function(fn, name) { 185 | Webdriver.prototype[name] = function() { 186 | const _this = this; 187 | const fargs = utils.varargs(arguments); 188 | this.emit('command', 'CALL', name + niceArgs(fargs.all)); 189 | const cb = function(err) { 190 | if (err) { 191 | err.message = '[' + name + niceArgs(fargs.all) + '] ' + err.message; 192 | if (fargs.callback) { fargs.callback(err); } 193 | } else { 194 | const cbArgs = __slice.call(arguments, 0); 195 | _this.emit('command', 'RESPONSE', name + niceArgs(fargs.all), 196 | niceResp(_.rest(cbArgs))); 197 | if (fargs.callback) { fargs.callback.apply(null, cbArgs); } 198 | } 199 | }; 200 | const args = fargs.all.concat([ cb ]); 201 | return fn.apply(this, args); 202 | }; 203 | }).value(); 204 | -------------------------------------------------------------------------------- /wd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wd", 3 | "description": "WebDriver/Selenium 2 node.js client", 4 | "keywords": [ 5 | "testing", 6 | "automation", 7 | "webdriver", 8 | "webdriverjs", 9 | "selenium" 10 | ], 11 | "version": "0.4.0", 12 | "author": "Adam Christian ", 13 | "contributors": [ 14 | "Ruben Daniels (https://github.com/javruben)", 15 | "Peter Braden (https://github.com/peterbraden)", 16 | "Seb Vincent (https://github.com/sebv)", 17 | "Peter 'Pita' Martischka (https://github.com/Pita)", 18 | "Jonathan Lipps (https://github.com/jlipps)", 19 | "Phil Sarin (https://github.com/pdsarin)", 20 | "Mathieu Sabourin (https://github.com/OniOni)", 21 | "Bjorn Tipling (https://github.com/btipling)", 22 | "Santiago Suarez Ordonez (https://github.com/santiycr)", 23 | "Bernard Kobos (https://github.com/bernii)", 24 | "Jason Carr (https://github.com/maudineormsby)", 25 | "Matti Schneider (https://github.com/MattiSG)" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/admc/wd.git" 30 | }, 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/admc/wd/issues" 34 | }, 35 | "engines": [ 36 | "node" 37 | ], 38 | "main": "./lib/main", 39 | "bin": { 40 | "wd": "./lib/bin.js" 41 | }, 42 | "directories": { 43 | "lib": "./lib" 44 | }, 45 | "dependencies": { 46 | 47 | }, 48 | "devDependencies": { 49 | "bdd-with-opts": "~1.0.0", 50 | "chai": "~2.3.0", 51 | "chai-as-promised": "~5.0.0", 52 | "colors": "~1.1.0", 53 | "dox": "~0.8.0", 54 | "express": "~4.12.4", 55 | "gulp": "~3.8.1", 56 | "gulp-jshint": "~1.11.0", 57 | "hbs": "~3.0.1", 58 | "http-proxy": "~1.11.1", 59 | "imageinfo": "~1.0.4", 60 | "istanbul": "~0.3.0", 61 | "jshint": "~2.7.0", 62 | "jshint-stylish": "~2.0.0", 63 | "mocha": "~2.2.5", 64 | "mu2": "~0.5.20", 65 | "nock": "~2.2.0", 66 | "node-uuid": "~1.4.1", 67 | "promise-simple": "~0.1.0", 68 | "run-sequence": "~1.1.0", 69 | "spawn-mocha-parallel": "~1.3.1", 70 | "sv-selenium": "~0.2.5", 71 | "yargs": "~3.9.1" 72 | }, 73 | "scripts": { 74 | "test": "gulp test-unit", 75 | "all_tests": "gulp test" 76 | } 77 | } 78 | --------------------------------------------------------------------------------