├── tests ├── .gitkeep └── BasicTest.php ├── src ├── Traits │ ├── Key.php │ ├── Util.php │ ├── Elm.php │ ├── Touch.php │ └── BaseConstants.php ├── Remote │ ├── Dummy.php │ └── AppiumRemoteDriver.php ├── TestCase │ ├── Session.php │ ├── MultiAction.php │ ├── Element.php │ └── TouchAction.php └── AppiumDriver.php ├── .bumpversion.cfg ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .travis.yml ├── cmd ├── Parser │ ├── Json │ │ ├── AppiumCommandExtra.json │ │ ├── AppiumCommandOverride.json │ │ ├── jsonwire-full.json │ │ └── AppiumCommandRoute.json │ ├── Helpers │ │ ├── Helper.php │ │ └── TextTable.php │ └── JsonParser.php ├── AppiumCodeceptionCLI.php └── Commands │ └── JsonParserCommand.php ├── TODO.md ├── bin └── ac-cli ├── package.json ├── .all-contributorsrc ├── RESOURCES.md ├── phpunit.xml ├── composer.json ├── LICENSE.md ├── Makefile ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── .gitignore ├── README.md └── yarn.lock /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Traits/Key.php: -------------------------------------------------------------------------------- 1 | {new_version} 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | - PHP Version: 6 | - Appium Version: 7 | - Codeception Version: 8 | 9 | ### Description: 10 | 11 | 12 | ### Steps To Reproduce: -------------------------------------------------------------------------------- /tests/BasicTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(true, true); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Remote/Dummy.php: -------------------------------------------------------------------------------- 1 | runApplication(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appium-driver-codeception", 3 | "version": "1.0.0", 4 | "description": "Appium driver for codeception for writing mobile tests.", 5 | "scripts": { 6 | "contributors:add": "./node_modules/.bin/all-contributors add", 7 | "contributors:generate": "./node_modules/.bin/all-contributors generate" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/me-io/appium-codeception.git" 12 | }, 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/me-io/appium-codeception/issues" 16 | }, 17 | "homepage": "https://github.com/me-io/appium-codeception#readme", 18 | "devDependencies": { 19 | "all-contributors-cli": "^4.10.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "appium-driver-codeception", 3 | "projectOwner": "me-io", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "Meabed", 12 | "name": "Mohamed Meabed", 13 | "avatar_url": "https://avatars0.githubusercontent.com/u/45731?v=3", 14 | "profile": "https://github.com/Meabed", 15 | "contributions": [ 16 | "code", 17 | "talk" 18 | ] 19 | }, 20 | { 21 | "login": "ziishaned", 22 | "name": "Zeeshan Ahmad", 23 | "avatar_url": "https://avatars2.githubusercontent.com/u/16267321?v=3", 24 | "profile": "https://github.com/ziishaned", 25 | "contributions": [ 26 | "code", 27 | "bug", 28 | "test", 29 | "doc" 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /RESOURCES.md: -------------------------------------------------------------------------------- 1 | ### Resources 2 | 3 | - https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js 4 | 5 | - https://w3c.github.io/webdriver/webdriver-spec.html 6 | 7 | - https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol 8 | 9 | - https://pypkg.com/pypi/appium-python-client/f/appium/webdriver/webdriver.py 10 | 11 | - http://www.rubydoc.info/gems/appium_lib/Appium/Core/Commands 12 | 13 | - http://www.rubydoc.info/gems/appium_lib/Appium/MultiTouch 14 | 15 | - http://www.rubydoc.info/gems/appium_lib/Appium/TouchAction 16 | 17 | - https://github.com/appium/appium-base-driver/blob/master/lib/mjsonwp/routes.js 18 | 19 | - https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md 20 | 21 | - https://github.com/appium/appium-dotnet-driver/blob/master/appium-dotnet-driver/Appium/AppiumCommand.cs 22 | 23 | - https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/MobileCommand.java -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | tests/ 19 | 20 | 21 | 22 | 23 | 24 | src 25 | 26 | 27 | -------------------------------------------------------------------------------- /cmd/Parser/Helpers/Helper.php: -------------------------------------------------------------------------------- 1 | substr($data, $startsAt, $endsAt - $startsAt), 29 | 'parameterName' => substr($data, ($startsAt + 1), $endsAt - ($startsAt + 1))]; 30 | } 31 | } while ($startsAt); 32 | 33 | 34 | return $results; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "me-io/appium-driver-codeception", 3 | "description": "appium driver for codeception framework", 4 | "keywords": [ 5 | "BDD", 6 | "automation testing", 7 | "acceptance testing", 8 | "functional testing", 9 | "unit testing", 10 | "tdd", 11 | "android automation", 12 | "ios automation", 13 | "appium", 14 | "appium driver", 15 | "codeception" 16 | ], 17 | "type": "library", 18 | "license": "MIT", 19 | "minimum-stability": "stable", 20 | "prefer-stable": true, 21 | "require": { 22 | "php": ">=7.0", 23 | "codeception/codeception": "~2", 24 | "phpunit/phpunit-selenium": "~4", 25 | "symfony/console": "~4", 26 | "squizlabs/php_codesniffer": "*" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "~6", 30 | "overtrue/phplint": "~1" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Appium\\": "src/", 35 | "Parser\\": "cmd/Parser/", 36 | "AppiumCodeceptionCLI\\": "cmd/" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Tests\\": "tests/" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 me.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /cmd/Parser/Json/AppiumCommandOverride.json: -------------------------------------------------------------------------------- 1 | { 2 | "/wd/hub/session/:sessionId/touch/click": { 3 | "POST": { 4 | "note": "override mismatch jsonwire file", 5 | "command": "touchClick" 6 | } 7 | }, 8 | "/wd/hub/session/:sessionId/appium/simulator/touch_id": { 9 | "POST": { 10 | "note": "override mismatch jsonwire file", 11 | "command": "simulatorTouchId" 12 | } 13 | }, 14 | "/wd/hub/session/:sessionId/appium/receive_async_response": { 15 | "POST": { 16 | "note": "override same command name in route.js", 17 | "command": "appReceiveAsyncResponse" 18 | } 19 | }, 20 | "/wd/hub/session/:sessionId/alert/text": { 21 | "GET": { 22 | "note": "override same command name in route.js", 23 | "command": "getAlertTextEx" 24 | }, 25 | "POST": { 26 | "note": "override same command name in route.js", 27 | "command": "setAlertTextEx" 28 | } 29 | }, 30 | "/wd/hub/session/:sessionId/alert/accept": { 31 | "POST": { 32 | "note": "override same command name in route.js", 33 | "command": "postAcceptAlertEx" 34 | } 35 | }, 36 | "/wd/hub/session/:sessionId/alert/dismiss": { 37 | "POST": { 38 | "note": "override same command name in route.js", 39 | "command": "postDismissAlertEx" 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /cmd/AppiumCodeceptionCLI.php: -------------------------------------------------------------------------------- 1 | commands[] = new JsonParserCommand(); 31 | } 32 | 33 | /** 34 | * Get all commands of the application. 35 | * 36 | * @return array 37 | */ 38 | public function getCommands() 39 | { 40 | return $this->commands; 41 | } 42 | 43 | /** 44 | * The function from where whole fun begins. 45 | * 46 | * @link https://gist.github.com/zeeshanu/e06c367b60f3b29a0e8ea87230f31c90 Steps to follow for generating routes 47 | * @link https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js Appium Base Driver Routes 48 | * @link http://phrogz.net/JS/NeatJSON/ Convert js var to json 49 | */ 50 | public function runApplication() 51 | { 52 | $application = new Application(self::APPLICATION_NAME, self::APPLICATION_VERSION); 53 | $application->addCommands($this->getCommands()); 54 | $application->run(); 55 | } 56 | } -------------------------------------------------------------------------------- /cmd/Commands/JsonParserCommand.php: -------------------------------------------------------------------------------- 1 | setName('generate:all') 25 | ->addUsage('generate:all ') 26 | ->setDescription('Generate all Commands from the json routes.') 27 | ->setHelp('Generate all Commands located in json found here: 28 | * https://github.com/appium/appium-base-driver/blob/master/lib/mjsonwp/routes.js 29 | * https://github.com/admc/wd/blob/master/doc/jsonwire-full.json 30 | '); 31 | } 32 | 33 | /** 34 | * Execute the command 35 | * 36 | * @param \Symfony\Component\Console\Input\InputInterface $input 37 | * @param \Symfony\Component\Console\Output\OutputInterface $output 38 | * 39 | * @return mixed 40 | */ 41 | protected function execute(InputInterface $input, OutputInterface $output) 42 | { 43 | $style = new SymfonyStyle($input, $output); 44 | $output->writeln('Start generating...'); 45 | (new JsonParser($style))->generate(); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hello, and thanks for contributing to Appium Driver Codeception! 2 | 3 | # Description 4 | 5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 6 | 7 | Fixes # (issue) 8 | 9 | ## Type of change 10 | 11 | Please delete options that are not relevant. 12 | 13 | - [ ] Bug fix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] This change requires a documentation update 17 | - [ ] Fixing typos 18 | 19 | # How Has This Been Tested? 20 | 21 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 22 | 23 | - [ ] Test A 24 | - [ ] Test B 25 | 26 | **Test Configuration**: 27 | - PHP Version: 28 | - Appium Version: 29 | - Codeception Version: 30 | 31 | # Checklist: 32 | 33 | - [ ] My code follows the style guidelines of this project 34 | - [ ] I have performed a self-review of my own code 35 | - [ ] I have commented my code, particularly in hard-to-understand areas 36 | - [ ] I have made corresponding changes to the documentation 37 | - [ ] My changes generate no new warnings 38 | - [ ] I have added tests that prove my fix is effective or that my feature works 39 | - [ ] New and existing unit tests pass locally with my changes 40 | - [ ] Any dependent changes have been merged and published in downstream modules -------------------------------------------------------------------------------- /src/TestCase/Session.php: -------------------------------------------------------------------------------- 1 | baseUrl = $baseUrl; 25 | parent::__construct($driver, $url, $baseUrl, $timeouts); 26 | } 27 | 28 | /** 29 | * @param array $value WebElement JSON object 30 | * 31 | * @return \PHPUnit_Extensions_Selenium2TestCase_Element 32 | */ 33 | public function elementFromResponseValue($value) 34 | { 35 | return \PHPUnit_Extensions_Selenium2TestCase_Element::fromResponseValue($value, $this->getSessionUrl()->descend('element'), $this->driver); 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | protected function initCommands() 42 | { 43 | $baseUrl = $this->baseUrl; 44 | $commands = parent::initCommands(); 45 | 46 | $commands['contexts'] = 'PHPUnit_Extensions_Selenium2TestCase_SessionCommand_GenericAccessor'; 47 | $commands['context'] = 'PHPUnit_Extensions_AppiumTestCase_SessionCommand_Context'; 48 | 49 | return $commands; 50 | } 51 | 52 | /** 53 | * @return \PHPUnit_Extensions_Selenium2TestCase_Driver|AppiumRemoteDriver 54 | */ 55 | public function getDriver() 56 | { 57 | return $this->driver; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | help: 4 | @echo "" 5 | @echo "Available tasks:" 6 | @echo " deps-update-dev Install dependencies" 7 | @echo " deps-update-prod Install dependencies" 8 | @echo " deps-install-dev Install dependencies" 9 | @echo " deps-install-prod Install dependencies" 10 | @echo " lint Run linter and code style checker" 11 | @echo " unit Run unit tests and generate" 12 | @echo " unit-cov Run unit tests and generate coverage" 13 | @echo " unit-html Run unit tests and generate coverage, html" 14 | @echo " test Run linter and unit tests" 15 | @echo " watch Run linter and unit tests when any of the source files change" 16 | @echo " all Install dependencies and run linter and unit tests" 17 | @echo "" 18 | 19 | deps-update-dev: 20 | composer update --prefer-dist --no-ansi --no-interaction --optimize-autoloader --ignore-platform-reqs 21 | 22 | deps-update-prod: 23 | composer update --prefer-dist --no-ansi --no-interaction --optimize-autoloader --ignore-platform-reqs --no-dev 24 | 25 | deps-install-dev: 26 | composer install --prefer-dist --no-ansi --no-interaction --optimize-autoloader --ignore-platform-reqs 27 | 28 | deps-install-prod: 29 | composer install --prefer-dist --no-ansi --no-interaction --optimize-autoloader --ignore-platform-reqs --no-dev 30 | 31 | lint: 32 | vendor/bin/phplint . --exclude=vendor/ 33 | vendor/bin/phpcs -p --standard=PSR2 --extensions=php --encoding=utf-8 --ignore=*/vendor/*,*/benchmarks/* . 34 | 35 | unit: 36 | vendor/bin/phpunit 37 | 38 | unit-cov: 39 | vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml 40 | 41 | unit-html: 42 | vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml --coverage-html=./report/ 43 | 44 | test: lint unit 45 | 46 | travis: lint unit 47 | 48 | all: deps-install-dev test 49 | 50 | .PHONY: help deps-update-dev deps-update-prod deps-install-dev deps-install-prod lint unit unit-html test travis all -------------------------------------------------------------------------------- /src/Remote/AppiumRemoteDriver.php: -------------------------------------------------------------------------------- 1 | seleniumServerUrl = $seleniumServerUrl; 25 | $this->seleniumServerRequestsTimeout = $timeout; 26 | } 27 | 28 | /** 29 | * @param array $desiredCapabilities 30 | * @param \PHPUnit_Extensions_Selenium2TestCase_URL $browserUrl 31 | * @return Session 32 | */ 33 | public function startSession( 34 | array $desiredCapabilities, 35 | \PHPUnit_Extensions_Selenium2TestCase_URL $browserUrl 36 | ) { 37 | $sessionCreation = $this->seleniumServerUrl->descend("/wd/hub/session"); 38 | $response = $this->curl('POST', $sessionCreation, [ 39 | 'desiredCapabilities' => $desiredCapabilities, 40 | ]); 41 | $sessionPrefix = $response->getURL(); 42 | 43 | $timeouts = new \PHPUnit_Extensions_Selenium2TestCase_Session_Timeouts( 44 | $this, 45 | $sessionPrefix->descend('timeouts'), 46 | $this->seleniumServerRequestsTimeout * 1000 47 | ); 48 | 49 | return new Session( 50 | $this, 51 | $sessionPrefix, 52 | $browserUrl, 53 | $timeouts 54 | ); 55 | } 56 | 57 | /** 58 | * @return \PHPUnit_Extensions_Selenium2TestCase_URL 59 | */ 60 | public function getServerUrl() 61 | { 62 | return $this->seleniumServerUrl; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/TestCase/MultiAction.php: -------------------------------------------------------------------------------- 1 | sessionUrl = $sessionUrl; 27 | $this->driver = $driver; 28 | $this->element = $element; 29 | $this->actions = []; 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param \Appium\TestCase\TouchAction $action 36 | * 37 | * @return $this 38 | */ 39 | public function add(TouchAction $action) 40 | { 41 | if (is_null($this->actions)) { 42 | $this->actions = []; 43 | } 44 | 45 | $this->actions[] = $action; 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 52 | */ 53 | public function perform() 54 | { 55 | $params = $this->getJSONWireGestures(); 56 | 57 | $url = $this->sessionUrl->descend('touch')->descend('multi')->descend('perform'); 58 | 59 | return $this->driver->curl('POST', $url, $params); 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getJSONWireGestures() 66 | { 67 | $actions = []; 68 | foreach ($this->actions as $action) { 69 | $actions[] = $action->getJSONWireGestures(); 70 | } 71 | 72 | $gestures = [ 73 | 'actions' => $actions, 74 | ]; 75 | if (!is_null($this->element)) { 76 | $gestures['elementId'] = $this->element->getId(); 77 | } 78 | 79 | return $gestures; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Traits/Util.php: -------------------------------------------------------------------------------- 1 | driverCommand(static::GET, BaseConstants::$GETSCREENSHOT); 19 | $screenshot = base64_decode($data); 20 | if ($save_as) { 21 | file_put_contents($save_as, $screenshot); 22 | } 23 | 24 | return $screenshot; 25 | } 26 | 27 | 28 | /** 29 | * 30 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 31 | */ 32 | public function launchAppiumApp() 33 | { 34 | /** @var \PHPUnit_Extensions_Selenium2TestCase_URL $sessionUrl */ 35 | $sessionUrl = $this->getSessionUrl(); 36 | /** @var AppiumRemoteDriver $driver */ 37 | $driver = $this->getDriver(); 38 | 39 | // appium/app/launch 40 | $url = $sessionUrl->descend('appium')->descend('app')->descend('launch'); 41 | $response = $driver->curl('POST', $url, null); 42 | 43 | return $response; 44 | } 45 | 46 | /** 47 | * Set the current geo location 48 | * @param $latitude 49 | * @param $longitude 50 | * @param $altitude 51 | * @return mixed 52 | * @usage $this->setLocation(100,150,200); 53 | * @author Anoop Ambunhi 54 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 55 | * 56 | */ 57 | public function setLocation($latitude, $longitude, $altitude) 58 | { 59 | $lat = strval($latitude); 60 | $lon = strval($longitude); 61 | $alt = strval($altitude); 62 | $data = [ 63 | 'location' => [ 64 | 'latitude' => $lat, 65 | 'longitude' => $lon, 66 | 'altitude' => $alt 67 | ] 68 | ]; 69 | return $this->driverCommand(BaseConstants::$POST, '/location', $data); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Only one feature or change per pull request 4 | 5 | Make pull requests only one feature or change at the time. Make pull requests from feature branch. Pull requests should not come from your master branch. 6 | 7 | For example you have fixed a bug. You also have optimized some code. Optimization is not related to a bug. These should be submitted as separate pull requests. This way I can easily choose what to include. It is also easier to understand the code changes. 8 | 9 | ## Write meaningful commit messages 10 | 11 | Proper commit message is full sentence. It starts with capital letter but does not end with period. Headlines do not end with period. The GitHub default `Update filename.js` is not enough. When needed include also longer explanation what the commit does. 12 | 13 | ``` 14 | Capitalized, short (50 chars or less) summary 15 | 16 | More detailed explanatory text, if necessary. Wrap it to about 72 17 | characters or so. In some contexts, the first line is treated as the 18 | subject of an email and the rest of the text as the body. The blank 19 | line separating the summary from the body is critical (unless you omit 20 | the body entirely); tools like rebase can get confused if you run the 21 | two together. 22 | ``` 23 | 24 | When in doubt see Tim Pope's blogpost [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 25 | 26 | ## Send coherent history 27 | 28 | Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 29 | 30 | ## Follow the existing coding standards 31 | 32 | When contributing to open source project it is polite to follow the original authors coding standars. They might be different than yours. It is not a holy war. This project uses **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 33 | 34 | ## Running Tests 35 | 36 | You can run individual tests either manually... 37 | 38 | ``` bash 39 | $ composer phplint 40 | $ composer phpcs 41 | $ composer phpunit 42 | ``` 43 | 44 | ... or automatically on every code change. You will need [entr](http://entrproject.org/) for this to work. 45 | 46 | ``` bash 47 | $ composer watch 48 | ``` -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ccc@me.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /src/TestCase/Element.php: -------------------------------------------------------------------------------- 1 | descend($value['ELEMENT']); 27 | 28 | return new static($driver, $url); 29 | } 30 | 31 | // override to return Appium element 32 | 33 | /** 34 | * @param \PHPUnit_Extensions_Selenium2TestCase_ElementCriteria $criteria 35 | * @return Element 36 | */ 37 | public function element(\PHPUnit_Extensions_Selenium2TestCase_ElementCriteria $criteria) 38 | { 39 | $value = $this->postCommand('element', $criteria); 40 | 41 | return Element::fromResponseValue($value, $this->getSessionUrl()->descend('element'), $this->driver); 42 | } 43 | 44 | /** 45 | * @param \PHPUnit_Extensions_Selenium2TestCase_ElementCriteria $criteria 46 | * @return array 47 | */ 48 | public function elements(\PHPUnit_Extensions_Selenium2TestCase_ElementCriteria $criteria) 49 | { 50 | $values = $this->postCommand('elements', $criteria); 51 | 52 | $elements = []; 53 | foreach ($values as $value) { 54 | $elements[] = Element::fromResponseValue($value, $this->getSessionUrl()->descend('element'), $this->driver); 55 | } 56 | 57 | return $elements; 58 | } 59 | 60 | /** 61 | * @param string $strategy 62 | * @param string $value 63 | * @return Element 64 | */ 65 | public function by($strategy, $value) 66 | { 67 | $el = $this->element($this->using($strategy)->value($value)); 68 | 69 | return $el; 70 | } 71 | 72 | /** 73 | * @return \PHPUnit_Extensions_Selenium2TestCase_URL|string 74 | */ 75 | protected function getSessionUrl() 76 | { 77 | return $this->url; 78 | } 79 | 80 | /** 81 | * @param $value 82 | * 83 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 84 | */ 85 | public function setValueImmediate($value) 86 | { 87 | $data = [ 88 | 'id' => $this->getId(), 89 | 'value' => $value, 90 | ]; 91 | $url = $this->getSessionUrl()->ascend()->ascend()->descend('appium')->descend('element')->descend($this->getId())->descend('value'); 92 | 93 | return $this->driver->curl('POST', $url, $data); 94 | } 95 | 96 | 97 | /** 98 | * @param $keys 99 | * 100 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 101 | */ 102 | public function replaceValue($keys) 103 | { 104 | $data = [ 105 | 'id' => $this->getId(), 106 | 'value' => [$keys], 107 | ]; 108 | $url = $this->getSessionUrl()->ascend()->ascend()->descend('appium')->descend('element')->descend($this->getId())->descend('replace_value'); 109 | 110 | return $this->driver->curl('POST', $url, $data); 111 | } 112 | 113 | /** 114 | * @return string content of this element 115 | */ 116 | public function getText() 117 | { 118 | $data = array( 119 | 'id' => $this->getId() 120 | ); 121 | $url = $this->getSessionUrl()->ascend()->ascend()->descend('element')->descend($this->getId())->descend('text'); 122 | $response = $this->driver->curl('GET', $url, $data); 123 | return $response->getValue(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /cmd/Parser/Helpers/TextTable.php: -------------------------------------------------------------------------------- 1 | 'L', 25 | 'type' => 'C', 26 | ]; 27 | 28 | /** 29 | * @param array $header The header array [key => label, ...] 30 | * @param array $content Content 31 | * @param bool $align Alignment optios [key => L|R|C, ...] 32 | */ 33 | public function __construct($header = null, $content = [], $align = false) 34 | { 35 | if ($header) { 36 | $this->header = $header; 37 | } elseif ($content) { 38 | foreach ($content[0] as $key => $value) { 39 | $this->header[$key] = $key; 40 | } 41 | } 42 | foreach ($this->header as $key => $label) { 43 | $this->len[$key] = strlen($label); 44 | } 45 | if (is_array($align)) { 46 | $this->setAlgin($align); 47 | } 48 | $this->addData($content); 49 | } 50 | 51 | /** 52 | * Overwrite the alignment array 53 | * 54 | * @param array $align Alignment optios [key => L|R|C, ...] 55 | */ 56 | public function setAlgin($align) 57 | { 58 | $this->align = $align; 59 | } 60 | 61 | /** 62 | * Add data to the table 63 | * 64 | * @param array $content Content 65 | * 66 | * @return \AppiumCodeceptionCLI\Parser\Helpers\TextTable 67 | */ 68 | public function addData($content) 69 | { 70 | foreach ($content as &$row) { 71 | foreach ($this->header as $key => $value) { 72 | if (!isset($row[$key])) { 73 | $row[$key] = '-'; 74 | } elseif (strlen($row[$key]) > $this->maxlen) { 75 | $this->len[$key] = $this->maxlen; 76 | $row[$key] = substr($row[$key], 0, $this->maxlen - 3) . '...'; 77 | } elseif (strlen($row[$key]) > $this->len[$key]) { 78 | $this->len[$key] = strlen($row[$key]); 79 | } 80 | } 81 | } 82 | $this->data = $this->data + $content; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Add a delimiter 89 | * 90 | * @return string 91 | */ 92 | private function renderDelimiter() 93 | { 94 | $res = '|'; 95 | foreach ($this->len as $key => $l) { 96 | $res .= (isset($this->align[$key]) && ($this->align[$key] == 'C' || $this->align[$key] == 'L') ? ':' : ' ') 97 | . str_repeat('-', $l) 98 | . (isset($this->align[$key]) && ($this->align[$key] == 'C' || $this->align[$key] == 'R') ? ':' : ' ') 99 | . '|'; 100 | } 101 | 102 | return $res . "\r\n"; 103 | } 104 | 105 | /** 106 | * Render a single row 107 | * 108 | * @param array $row 109 | * 110 | * @return string 111 | */ 112 | private function renderRow($row) 113 | { 114 | $res = '|'; 115 | foreach ($this->len as $key => $l) { 116 | $res .= ' ' . $row[$key] . ($l > strlen($row[$key]) ? str_repeat(' ', $l - strlen($row[$key])) : '') . ' |'; 117 | } 118 | 119 | return $res . "\r\n"; 120 | } 121 | 122 | /** 123 | * Render the table 124 | * 125 | * @param array $content Additional table content 126 | * 127 | * @return string 128 | */ 129 | public function render($content = []) 130 | { 131 | $this->addData($content); 132 | $res = $this->renderRow($this->header) 133 | . $this->renderDelimiter(); 134 | foreach ($this->data as $row) { 135 | $res .= $this->renderRow($row); 136 | } 137 | 138 | return $res; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### NetBeans template 3 | nbproject/private/ 4 | build/ 5 | nbbuild/ 6 | dist/ 7 | nbdist/ 8 | nbactions.xml 9 | .nb-gradle/ 10 | ### Linux template 11 | *~ 12 | 13 | # temporary files which can be created if a process still has a handle open of a deleted file 14 | .fuse_hidden* 15 | 16 | # KDE directory preferences 17 | .directory 18 | 19 | # Linux trash folder which might appear on any partition or disk 20 | .Trash-* 21 | ### JetBrains template 22 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 23 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 24 | 25 | # User-specific stuff: 26 | .idea/workspace.xml 27 | .idea/tasks.xml 28 | .idea/dictionaries 29 | .idea/vcs.xml 30 | .idea/jsLibraryMappings.xml 31 | 32 | # Sensitive or high-churn files: 33 | .idea/dataSources.ids 34 | .idea/dataSources.xml 35 | .idea/dataSources.local.xml 36 | .idea/sqlDataSources.xml 37 | .idea/dynamic.xml 38 | .idea/uiDesigner.xml 39 | 40 | # Gradle: 41 | .idea/gradle.xml 42 | .idea/libraries 43 | 44 | # Mongo Explorer plugin: 45 | .idea/mongoSettings.xml 46 | 47 | ## File-based project format: 48 | *.iws 49 | 50 | ## Plugin-specific files: 51 | 52 | # IntelliJ 53 | /out/ 54 | 55 | # mpeltonen/sbt-idea plugin 56 | .idea_modules/ 57 | 58 | # JIRA plugin 59 | atlassian-ide-plugin.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | fabric.properties 66 | ### Symfony template 67 | # Cache and logs (Symfony2) 68 | /app/cache/* 69 | /app/logs/* 70 | !app/cache/.gitkeep 71 | !app/logs/.gitkeep 72 | 73 | # Email spool folder 74 | /app/spool/* 75 | 76 | # Cache, session files and logs (Symfony3) 77 | /var/cache/* 78 | /var/logs/* 79 | /var/sessions/* 80 | !var/cache/.gitkeep 81 | !var/logs/.gitkeep 82 | !var/sessions/.gitkeep 83 | 84 | # Parameters 85 | /app/config/parameters.yml 86 | /app/config/parameters.ini 87 | 88 | # Managed by Composer 89 | /app/bootstrap.php.cache 90 | /var/bootstrap.php.cache 91 | !bin/console 92 | !bin/symfony_requirements 93 | /vendor/ 94 | 95 | # Assets and user uploads 96 | /web/bundles/ 97 | /web/uploads/ 98 | 99 | # Assets managed by Bower 100 | /web/assets/vendor/ 101 | 102 | # PHPUnit 103 | /app/phpunit.xml 104 | 105 | # Build data 106 | /build/ 107 | 108 | # Composer PHAR 109 | /composer.phar 110 | 111 | # Backup entities generated with doctrine:generate:entities command 112 | */Entity/*~ 113 | ### SublimeText template 114 | # cache files for sublime text 115 | *.tmlanguage.cache 116 | *.tmPreferences.cache 117 | *.stTheme.cache 118 | 119 | # workspace files are user-specific 120 | *.sublime-workspace 121 | 122 | # project files should be checked into the repository, unless a significant 123 | # proportion of contributors will probably not be using SublimeText 124 | # *.sublime-project 125 | 126 | # sftp configuration file 127 | sftp-config.json 128 | 129 | # Package control specific files 130 | Package Control.last-run 131 | Package Control.ca-list 132 | Package Control.ca-bundle 133 | Package Control.system-ca-bundle 134 | Package Control.cache/ 135 | Package Control.ca-certs/ 136 | bh_unicode_properties.cache 137 | 138 | # Sublime-github package stores a github token in this file 139 | # https://packagecontrol.io/packages/sublime-github 140 | GitHub.sublime-settings 141 | ### Vim template 142 | # swap 143 | [._]*.s[a-w][a-z] 144 | [._]s[a-w][a-z] 145 | # session 146 | Session.vim 147 | # temporary 148 | .netrwhist 149 | *~ 150 | # auto-generated tag files 151 | tags 152 | ### OSX template 153 | *.DS_Store 154 | .AppleDouble 155 | .LSOverride 156 | 157 | # Icon must end with two \r 158 | Icon 159 | 160 | # Thumbnails 161 | ._* 162 | 163 | # Files that might appear in the root of a volume 164 | .DocumentRevisions-V100 165 | .fseventsd 166 | .Spotlight-V100 167 | .TemporaryItems 168 | .Trashes 169 | .VolumeIcon.icns 170 | .com.apple.timemachine.donotpresent 171 | 172 | # Directories potentially created on remote AFP share 173 | .AppleDB 174 | .AppleDesktop 175 | Network Trash Folder 176 | Temporary Items 177 | .apdisk 178 | ### Vagrant template 179 | .vagrant/ 180 | ### Windows template 181 | # Windows image file caches 182 | Thumbs.db 183 | ehthumbs.db 184 | 185 | # Folder config file 186 | Desktop.ini 187 | 188 | # Recycle Bin used on file shares 189 | $RECYCLE.BIN/ 190 | 191 | # Windows Installer files 192 | *.cab 193 | *.msi 194 | *.msm 195 | *.msp 196 | 197 | # Windows shortcuts 198 | *.lnk 199 | 200 | composer.phar 201 | package/codecept.phar 202 | .idea 203 | .phplint-cache 204 | node_modules -------------------------------------------------------------------------------- /src/Traits/Elm.php: -------------------------------------------------------------------------------- 1 | AppiumDriver, $this->getSessionUrl()); 20 | } 21 | 22 | 23 | /** 24 | * @param Element $element that accepts a string 25 | * @param string $keys send to $element 26 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 27 | */ 28 | public function sendKeys($element, $keys) 29 | { 30 | return $element->setValueImmediate($keys); 31 | } 32 | 33 | /** 34 | * @param $value 35 | * 36 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 37 | */ 38 | public function byIOSUIAutomation($value) 39 | { 40 | return $this->TestCaseElm()->by('-ios uiautomation', $value); 41 | } 42 | 43 | /** 44 | * @param $value 45 | * 46 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 47 | */ 48 | public function byIOSPredicateString($value) 49 | { 50 | return $this->TestCaseElm()->by('-ios predicate string', $value); 51 | } 52 | 53 | /** 54 | * @param $value 55 | * 56 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 57 | */ 58 | public function byAndroidUIAutomator($value) 59 | { 60 | return $this->TestCaseElm()->by('-android uiautomator', $value); 61 | } 62 | 63 | /** 64 | * @param $value 65 | * 66 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 67 | */ 68 | public function byAccessibilityId($value) 69 | { 70 | return $this->TestCaseElm()->by('accessibility id', $value); 71 | } 72 | 73 | /** 74 | * @param string $value e.g. 'container' 75 | * 76 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 77 | */ 78 | public function byClassName($value) 79 | { 80 | return $this->TestCaseElm()->by('class name', $value); 81 | } 82 | 83 | /** 84 | * @param string $value e.g. 'div.container' 85 | * 86 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 87 | */ 88 | public function byCssSelector($value) 89 | { 90 | return $this->TestCaseElm()->by('css selector', $value); 91 | } 92 | 93 | /** 94 | * @param string $value e.g. 'uniqueId' 95 | * 96 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 97 | */ 98 | public function byId($value) 99 | { 100 | return $this->TestCaseElm()->by('id', $value); 101 | } 102 | 103 | /** 104 | * @param string $value e.g. 'Link text' 105 | * 106 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 107 | */ 108 | public function byLinkText($value) 109 | { 110 | return $this->TestCaseElm()->by('link text', $value); 111 | } 112 | 113 | /** 114 | * @param string $value e.g. 'Link te' 115 | * 116 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 117 | */ 118 | public function byPartialLinkText($value) 119 | { 120 | return $this->TestCaseElm()->by('partial link text', $value); 121 | } 122 | 123 | /** 124 | * @param string $value e.g. 'email_address' 125 | * 126 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 127 | */ 128 | public function byName($value) 129 | { 130 | return $this->TestCaseElm()->by('name', $value); 131 | } 132 | 133 | /** 134 | * @param string $value e.g. 'body' 135 | * 136 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 137 | */ 138 | public function byTag($value) 139 | { 140 | return $this->TestCaseElm()->by('tag name', $value); 141 | } 142 | 143 | /** 144 | * @param string $value e.g. '/div[@attribute="value"]' 145 | * 146 | * @return \Appium\TestCase\Element|\PHPUnit_Extensions_Selenium2TestCase_Element 147 | */ 148 | public function byXPath($value) 149 | { 150 | return $this->TestCaseElm()->by('xpath', $value); 151 | } 152 | 153 | /** 154 | * @param $value 155 | * @return \Appium\TestCase\Element 156 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 157 | */ 158 | public function byIOSClassChain($value) 159 | { 160 | return $this->TestCaseElm()->by('-ios class chain', $value); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/TestCase/TouchAction.php: -------------------------------------------------------------------------------- 1 | sessionUrl = $sessionUrl; 24 | $this->driver = $driver; 25 | $this->actions = []; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @return \Appium\TestCase\Element 32 | */ 33 | public function TestCaseElement() 34 | { 35 | return new Element($this->driver, $this->sessionUrl); 36 | } 37 | 38 | /** 39 | * @param $params 40 | * 41 | * @return $this 42 | */ 43 | public function tap($params) 44 | { 45 | $options = $this->getOptions($params); 46 | 47 | if (array_key_exists('count', $params)) { 48 | $options['count'] = $params['count']; 49 | } else { 50 | $options['count'] = 1; 51 | } 52 | 53 | $this->addAction('tap', $options); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @param $params 60 | * 61 | * @return $this 62 | */ 63 | public function press($params) 64 | { 65 | $options = $this->getOptions($params); 66 | 67 | $this->addAction('press', $options); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @param $params 74 | * 75 | * @return $this 76 | */ 77 | public function longPress($params) 78 | { 79 | $options = $this->getOptions($params); 80 | 81 | if (array_key_exists('duration', $params)) { 82 | $options['duration'] = $params['duration']; 83 | } else { 84 | $options['duration'] = 800; 85 | } 86 | 87 | $this->addAction('longPress', $options); 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param $params 94 | * 95 | * @return $this 96 | */ 97 | public function moveTo($params) 98 | { 99 | $options = $this->getOptions($params); 100 | 101 | $this->addAction('moveTo', $options); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * @param $params 108 | * 109 | * @return $this 110 | */ 111 | public function wait($params) 112 | { 113 | $options = []; 114 | 115 | if (gettype($params) == 'array') { 116 | if (array_key_exists('ms', $params)) { 117 | $options['ms'] = $params['ms']; 118 | } else { 119 | $options['ms'] = 0; 120 | } 121 | } else { 122 | $options['ms'] = $params; 123 | } 124 | 125 | $this->addAction('wait', $options); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * @return $this 132 | */ 133 | public function release() 134 | { 135 | $this->addAction('release', []); 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * @return \PHPUnit_Extensions_Selenium2TestCase_Response 142 | */ 143 | public function perform() 144 | { 145 | $params = [ 146 | 'actions' => $this->actions, 147 | ]; 148 | $url = $this->sessionUrl->descend('touch')->descend('perform'); 149 | 150 | return $this->driver->curl('POST', $url, $params); 151 | } 152 | 153 | /** 154 | * @return array 155 | */ 156 | public function getJSONWireGestures() 157 | { 158 | $actions = []; 159 | foreach ($this->actions as $action) { 160 | $actions[] = $action; 161 | } 162 | 163 | return $actions; 164 | } 165 | 166 | /** 167 | * Get options and create element depending on the selector type sent in the options. 168 | * 169 | * @param $params 170 | * 171 | * @return array 172 | */ 173 | public function getOptions($params) 174 | { 175 | $opts = []; 176 | 177 | if (array_key_exists('element', $params) && $params['element'] != null) { 178 | if (is_array($params['element']) && isset($params['element']['type']) && isset($params['element']['value'])) { 179 | /* 180 | * Select the type of the selector sent in the options: ['element' => ['type' => 'xpath', 'value' => '//your_xpath']] 181 | */ 182 | $opts['element'] = $this->TestCaseElement()->by($params['element']['type'], $params['element']['value'])->getId(); 183 | } 184 | } 185 | 186 | # it makes no sense to have x but no y, or vice versa. 187 | if (array_key_exists('x', $params) && array_key_exists('y', $params)) { 188 | $opts['x'] = $params['x']; 189 | $opts['y'] = $params['y']; 190 | } 191 | 192 | return $opts; 193 | } 194 | 195 | /** 196 | * @param $action 197 | * @param $options 198 | * 199 | * @return array 200 | */ 201 | public function addAction($action, $options) 202 | { 203 | $gesture = [ 204 | 'action' => $action, 205 | 'options' => $options, 206 | ]; 207 | 208 | $this->actions[] = $gesture; 209 | 210 | return $this->actions; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/Traits/Touch.php: -------------------------------------------------------------------------------- 1 | getSessionUrl(), $this->getDriver()); 21 | } 22 | 23 | /** 24 | * @return \Appium\TestCase\MultiAction 25 | */ 26 | public function getMultiTouchAction() 27 | { 28 | return new MultiAction($this->getSessionUrl(), $this->getDriver()); 29 | } 30 | 31 | /** 32 | * Swipe from one point to another point, for an optional duration. 33 | * convenience method added to Appium (NOT Selenium 3) 34 | * 35 | * @link https://pypkg.com/pypi/appium-python-client/f/appium/webdriver/webdriver.py 36 | * 37 | * @param string startX x-percent at which to start 38 | * @param string startY y-percent at which to start 39 | * @param string endX x-percent at which to end 40 | * @param string endY y-percent at which to end 41 | * @param int $duration (optional) time to take the swipe in ms 42 | * 43 | * @return mixed 44 | */ 45 | public function swipe($startX, $startY, $endX, $endY, $duration = 800) 46 | { 47 | $action = $this->getTouchAction(); 48 | $action->press(['x' => $startX, 'y' => $startY]) 49 | ->wait($duration) 50 | ->moveTo(['x' => $endX, 'y' => $endY]) 51 | ->wait($duration) 52 | ->release() 53 | ->perform(); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Flick from one point to another point. 60 | * convenience method added to Appium (NOT Selenium 3) 61 | * 62 | * @link https://pypkg.com/pypi/appium-python-client/f/appium/webdriver/webdriver.py 63 | * 64 | * @param string startX x-percent at which to start 65 | * @param string startY y-percent at which to start 66 | * @param string endX x-percent at which to end 67 | * @param string endY y-percent at which to end 68 | * 69 | * @return mixed 70 | */ 71 | public function flickFromTo($startX, $startY, $endX, $endY) 72 | { 73 | $action = $this->getTouchAction(); 74 | $action->press(['x' => $startX, 'y' => $startY]) 75 | ->moveTo(['x' => $endX, 'y' => $endY]) 76 | ->release() 77 | ->perform(); 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * Scrolls from one element to another 84 | * convenience method added to Appium (NOT Selenium 3) 85 | * 86 | * @link https://pypkg.com/pypi/appium-python-client/f/appium/webdriver/webdriver.py 87 | * @usage $this->scroll(['type'=>'id','value'=>'header_bar'],['type'=>'xpath','value'=>'div1[1]>classA>textare']); 88 | * 89 | * @param array $originElArray the element from which to being scrolling 90 | * @param array $destinationElArray the element to scroll to 91 | * @param int $duration 92 | * 93 | * @return $this 94 | */ 95 | public function scroll($originElArray, $destinationElArray, $duration = 500) 96 | { 97 | $action = $this->getTouchAction(); 98 | $action->press(['element' => $originElArray]) 99 | ->wait($duration) 100 | ->moveTo(['element' => $destinationElArray]) 101 | ->release() 102 | ->perform(); 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Drag the origin element to the destination element 109 | * convenience method added to Appium (NOT Selenium 3) 110 | * 111 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 112 | * 113 | * @param array $originElArray 114 | * @param array $destinationElArray 115 | * @param int $duration 116 | * 117 | * @return \Appium\Traits\Touch 118 | */ 119 | public function dragAndDrop($originElArray, $destinationElArray, $duration = 500) 120 | { 121 | $action = $this->getTouchAction(); 122 | $action->longPress(['element' => $originElArray]) 123 | ->wait($duration) 124 | ->moveTo(['element' => $destinationElArray]) 125 | ->release() 126 | ->perform(); 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Taps on an particular place with up to five fingers, holding for a certain time 133 | * convenience method added to Appium (NOT Selenium 3) 134 | * 135 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 136 | * @usage $this->tap([(100, 20), (100, 60), (100, 100)], 500); 137 | * 138 | * @param array $positions 139 | * @param int $duration 140 | * 141 | * @return \Appium\Traits\Touch 142 | */ 143 | public function tap($positions, $duration = 500) 144 | { 145 | if (count($positions) == 1) { 146 | $action = $this->getTouchAction(); 147 | 148 | $options = [ 149 | 'x' => $positions[0][0], 150 | 'y' => $positions[0][1], 151 | ]; 152 | 153 | if ($duration) { 154 | $options['duration'] = $duration; 155 | $action->longPress($options)->release()->perform(); 156 | } else { 157 | $action->tap($options)->release()->perform(); 158 | } 159 | } else { 160 | $multiTouchAction = $this->getMultiTouchAction(); 161 | foreach ($positions as $position) { 162 | $action = $this->getTouchAction(); 163 | 164 | $options = [ 165 | 'x' => $position[0][0], 166 | 'y' => $position[0][1], 167 | ]; 168 | 169 | if ($duration) { 170 | $options['duration'] = $duration; 171 | $action->longPress($options)->release()->perform(); 172 | } else { 173 | $action->tap($options)->release()->perform(); 174 | } 175 | $multiTouchAction->add($action); 176 | } 177 | $multiTouchAction->perform(); 178 | } 179 | 180 | return $this; 181 | } 182 | 183 | /** 184 | * Pinch on an element a certain amount 185 | * convenience method added to Appium (NOT Selenium 3) 186 | * 187 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 188 | * @usage $this->pinch($element) 189 | * 190 | * @param null $element the element to pinch 191 | * @param int $percent amount to pinch. Defaults to 200% 192 | * @param int $steps number of steps in the pinch action 193 | * 194 | * @return \Appium\Traits\Touch 195 | */ 196 | public function pinch($element = null, $percent = 200, $steps = 50) 197 | { 198 | if ($element) { 199 | $element = $element['id']; 200 | } 201 | 202 | $options = [ 203 | 'element' => $element, 204 | 'percent' => $percent, 205 | 'steps' => $steps, 206 | ]; 207 | 208 | $this->execute([ 209 | 'mobile: pinchClose', 210 | $options, 211 | ]); 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Pinch on an element a certain amount 218 | * convenience method added to Appium (NOT Selenium 3) 219 | * 220 | * @link https://github.com/appium/python-client/blob/master/appium/webdriver/webdriver.py 221 | * @usage $this->zoom($element) 222 | * 223 | * @param null $element the element to zoom 224 | * @param int $percent amount to pinch. Defaults to 200% 225 | * @param int $steps number of steps in the pinch action 226 | * 227 | * @return \Appium\Traits\Touch 228 | */ 229 | public function zoom($element = null, $percent = 200, $steps = 50) 230 | { 231 | if ($element) { 232 | $element = $element['id']; 233 | } 234 | 235 | $options = [ 236 | 'element' => $element, 237 | 'percent' => $percent, 238 | 'steps' => $steps, 239 | ]; 240 | 241 | $this->execute([ 242 | 'mobile: pinchOpen', 243 | $options, 244 | ]); 245 | 246 | return $this; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Appium Driver for Codeception 3 |

4 |

Appium driver for codeception for writing mobile tests.

5 | 6 |
7 | 8 | [![Build Status][build-badge]][build] 9 | [![downloads][downloads-badge]][downloads] 10 | [![MIT License][license-badge]][license] 11 | [![Donate][donate-badge]][donate] 12 | 13 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors) 14 | [![PRs Welcome][prs-badge]][prs] 15 | [![Code of Conduct][coc-badge]][coc] 16 | [![Watch on GitHub][github-watch-badge]][github-watch] 17 | [![Star on GitHub][github-star-badge]][github-star] 18 | [![Tweet][twitter-badge]][twitter] 19 | 20 | ## Requirement 21 | 22 | 1. PHP >= 7.0 23 | 2. [Appium](http://appium.io/) 24 | 3. [Inspect App with Appium Desktop](https://medium.com/@eliasnogueira/inspect-an-app-with-the-new-appium-desktop-8ce4dc9aa95c) 25 | 4. Devices: 26 | * **Android** 27 | * [Setup Android SDK on Mac](https://gist.github.com/agrcrobles/165ac477a9ee51198f4a870c723cd441) 28 | * **iOS** 29 | * Install Xcode from the following [link](https://developer.apple.com/xcode/) or run the following command 30 | inside your terminal: 31 | ```bash 32 | xcode-select --install 33 | ``` 34 | * Install the Carthage dependency manager: 35 | ```bash 36 | brew install carthage 37 | ``` 38 | 39 | ## Table of Contents 40 | 41 | * [Install](#install) 42 | * [Tests](#tests) 43 | * [Writing tests for Android](#writing-tests-for-android) 44 | * [Writing tests for iOS](#writing-tests-for-ios) 45 | * [Generating Actor classes](#generating-actor-classes) 46 | * [Your First Android Test](#your-first-android-test) 47 | * [Your First iOS Test](#your-first-ios-test) 48 | * [Running tests](#running-tests) 49 | 50 | ## Install 51 | 52 | Just add `me-io/appium-driver-codeception` to your project's composer.json file: 53 | 54 | ```json 55 | { 56 | "require": { 57 | "me-io/appium-driver-codeception": "~1" 58 | } 59 | } 60 | ``` 61 | 62 | and then run `composer install`. This will install codeception appium driver and all it's dependencies. Or run the following command 63 | 64 | ```bash 65 | composer require me-io/appium-driver-codeception 66 | ``` 67 | 68 | ## Tests 69 | 70 | Now lets run the following command at the root directory of your project: 71 | 72 | ```bash 73 | codecept bootstrap 74 | ``` 75 | 76 | This command will creates a configuration file for codeception and tests directory and default test suites. 77 | 78 | ### Writing tests for Android 79 | 80 | Now, lets create a new configuration file `android.suite.yml` inside tests directory and put the following contents inside of it. 81 | 82 | ```yml 83 | class_name: AndroidGuy 84 | modules: 85 | enabled: 86 | # Enable appium driver 87 | - \Appium\AppiumDriver 88 | - Asserts 89 | config: 90 | # Configuration for appium driver 91 | \Appium\AppiumDriver: 92 | host: 0.0.0.0 93 | port: 4723 94 | dummyRemote: false 95 | resetAfterSuite: true 96 | resetAfterCest: false 97 | resetAfterTest: false 98 | resetAfterStep: false 99 | capabilities: 100 | platformName: 'Android' 101 | deviceName: 'Android device' 102 | automationName: 'Appium' 103 | appPackage: io.selendroid.testapp 104 | fullReset: false 105 | noReset: false 106 | newCommandTimeout: 7200 107 | nativeInstrumentsLib: true 108 | connection_timeout: 500 109 | request_timeout: 500 110 | autoAcceptAlerts: true 111 | appActivity: io.selendroid.testapp.HomeScreenActivity 112 | skipUnlock: true 113 | ``` 114 | 115 | > **Note**: `deviceName` should be set as `Android device` only for real device. For Android Emulator use the name of the virtual device. 116 | 117 | 118 | ### Writing tests for iOS 119 | 120 | Now, lets create a new configuration file `ios.suite.yml` inside tests directory and put the following contents inside of it. 121 | 122 | ```yml 123 | class_name: IosGuy 124 | modules: 125 | enabled: 126 | # Enable appium driver 127 | - \Appium\AppiumDriver 128 | - Asserts 129 | config: 130 | # Configuration for appium driver 131 | \Appium\AppiumDriver: 132 | host: 0.0.0.0 133 | port: 4723 134 | dummyRemote: false 135 | resetAfterSuite: true 136 | resetAfterCest: false 137 | resetAfterTest: false 138 | resetAfterStep: false 139 | capabilities: 140 | # PATH OF YOUR APP (something like /Users/username/Documents/ios.app) 141 | app: '' 142 | # xcideOrgId is Apple developer team identifier string. 143 | xcodeOrgId: '' 144 | # xcodeSigningId is a string representing a signing certificate. iPhone Developer by default. 145 | xcodeSigningId: 'iPhone Developer' 146 | platformName: 'iOS' 147 | platformVersion: '11.2' 148 | deviceName: 'iPhone8' 149 | # Your device udid 150 | udid: '' 151 | useNewWDA: false 152 | newCommandTimeout: 7200 153 | automationName: 'XCUITest' 154 | autoAcceptAlerts: true 155 | fullReset: false 156 | noReset: true 157 | nativeInstrumentsLib: true 158 | connection_timeout: 500 159 | request_timeout: 500 160 | skipUnlock: true 161 | clearSystemFiles: true 162 | showIOSLog: true 163 | ``` 164 | 165 | ### Generating Actor classes 166 | 167 | Now we need to generate actor class for the `AndroidGuy`/`IosGuy` that we defined in `android.suite.yml`/`ios.suite.yml`. To generate the actor class for `AndroidGuy`/`IosGuy` run the following command inside your terminal: 168 | 169 | ```bash 170 | codecept build 171 | ``` 172 | 173 | ### Your First Android Test 174 | 175 | To create your first android test create a new directory `android` inside `tests` folder. After creating the `android` folder create a new file `FirstAndroidCest.php` and put the following contents inside of it: 176 | 177 | ```php 178 | class FirstAndroidCest 179 | { 180 | public function changeLanguage(AndroidGuy $I) 181 | { 182 | $I->implicitWait([ 183 | 'ms' => 3500, 184 | ]); 185 | $text = $I->byId('id_of_button')->getText(); 186 | $I->assertEquals('Hello, World!', $text); 187 | } 188 | } 189 | ``` 190 | 191 | ### Your First iOS Test 192 | 193 | To create your first iOS test create a new directory `ios` inside `tests` folder. After creating the `ios` directory create a new file `FirstIosCest.php` and put the following contents inside of it: 194 | 195 | ```php 196 | class FirstIosCest 197 | { 198 | public function lockPhone(Ios $I) 199 | { 200 | $I->implicitWait([ 201 | 'ms' => 10000, 202 | ]); 203 | $I->assertEquals('Hello, World!', 'Hello, World!'); 204 | $I->amGoingTo("lock phone"); 205 | $I->lock([null]); 206 | } 207 | } 208 | ``` 209 | 210 | ### Running tests 211 | 212 | Run the appium server by running the following command: 213 | 214 | ```bash 215 | appium 216 | ``` 217 | > **NOTE:** If you want to change IP/Port run the appium command like this: 218 | ``` 219 | appium -a -p 220 | ``` 221 | 222 | After running the appium server now you need to start android emulator and install the application that you want to test. If you don't know how to start the emulator you can follow the following guide [Setup Genymotion Android Emulators on Mac OS 223 | ](https://shankargarg.wordpress.com/2016/02/25/setup-genymotion-android-emulators-on-mac-os/) 224 | 225 | Now run the following command inside your terminal to run the tests: 226 | 227 | ```bash 228 | # For Android 229 | codecept run android FirstAndroidCest.php --steps 230 | 231 | # For iOS 232 | codecept run ios FirstIosCest.php --steps 233 | ``` 234 | 235 | > **Note**: While following the steps that are mentioned here if you get `codecept command not found` error try to run `codecept` command like this `./vendor/bin/codecept`. 236 | 237 | ## Contributors 238 | 239 | A huge thanks to all of our contributors: 240 | 241 | 242 | 243 | | [
Mohamed Meabed](https://github.com/Meabed)
[💻](https://github.com/me-io/appium-driver-codeception/commits?author=Meabed "Code") [📢](#talk-Meabed "Talks") | [
Zeeshan Ahmad](https://github.com/ziishaned)
[💻](https://github.com/me-io/appium-driver-codeception/commits?author=ziishaned "Code") [🐛](https://github.com/me-io/appium-driver-codeception/issues?q=author%3Aziishaned "Bug reports") [⚠️](https://github.com/me-io/appium-driver-codeception/commits?author=ziishaned "Tests") [📖](https://github.com/me-io/appium-driver-codeception/commits?author=ziishaned "Documentation") | 244 | | :---: | :---: | 245 | 246 | 247 | 248 | ## License 249 | 250 | The code is available under the [MIT license](LICENSE.md). 251 | 252 | [build-badge]: https://img.shields.io/travis/me-io/appium-driver-codeception.svg?style=flat-square 253 | [build]: https://travis-ci.org/me-io/appium-driver-codeception 254 | [downloads-badge]: https://img.shields.io/packagist/dm/me-io/appium-driver-codeception.svg?style=flat-square 255 | [downloads]: https://packagist.org/packages/me-io/appium-driver-codeception/stats 256 | [license-badge]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 257 | [license]: https://github.com/me-io/appium-driver-codeception/blob/master/LICENSE.md 258 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 259 | [prs]: http://makeapullrequest.com 260 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 261 | [coc]: https://github.com/me-io/appium-driver-codeception/blob/master/CODE_OF_CONDUCT.md 262 | [github-watch-badge]: https://img.shields.io/github/watchers/me-io/appium-driver-codeception.svg?style=social 263 | [github-watch]: https://github.com/me-io/appium-driver-codeception/watchers 264 | [github-star-badge]: https://img.shields.io/github/stars/me-io/appium-driver-codeception.svg?style=social 265 | [github-star]: https://github.com/me-io/appium-driver-codeception/stargazers 266 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20appium-driver-codeception!%20https://github.com/me-io/appium-driver-codeception%20%F0%9F%91%8D 267 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/me-io/appium-driver-codeception.svg?style=social 268 | [donate-badge]: https://img.shields.io/badge/paypal-donate-179BD7.svg?style=flat-squares 269 | [donate]: https://www.paypal.me/meabed 270 | -------------------------------------------------------------------------------- /cmd/Parser/Json/jsonwire-full.json: -------------------------------------------------------------------------------- 1 | { 2 | "GET /status": "Query the server's current status.", 3 | "POST /session": "Create a new session.", 4 | "GET /sessions": "Returns a list of the currently active sessions.", 5 | "GET /session/:sessionId": "Retrieve the capabilities of the specified session.", 6 | "DELETE /session/:sessionId": "Delete the session.", 7 | "POST /session/:sessionId/timeouts": "Configure the amount of time that a particular type of operation can execute for before they are aborted and a |Timeout| error is returned to the client.", 8 | "POST /session/:sessionId/timeouts/async_script": "Set the amount of time, in milliseconds, that asynchronous scripts executed by /session/:sessionId/execute_async are permitted to run before they are aborted and a |Timeout| error is returned to the client.", 9 | "POST /session/:sessionId/timeouts/implicit_wait": "Set the amount of time the driver should wait when searching for elements.", 10 | "GET /session/:sessionId/window_handle": "Retrieve the current window handle.", 11 | "GET /session/:sessionId/window_handles": "Retrieve the list of all window handles available to the session.", 12 | "GET /session/:sessionId/url": "Retrieve the URL of the current page.", 13 | "POST /session/:sessionId/url": "Navigate to a new URL.", 14 | "POST /session/:sessionId/forward": "Navigate forwards in the browser history, if possible.", 15 | "POST /session/:sessionId/back": "Navigate backwards in the browser history, if possible.", 16 | "POST /session/:sessionId/refresh": "Refresh the current page.", 17 | "POST /session/:sessionId/execute": "Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame.", 18 | "POST /session/:sessionId/execute_async": "Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame.", 19 | "GET /session/:sessionId/screenshot": "Take a screenshot of the current page.", 20 | "GET /session/:sessionId/ime/available_engines": "List all available engines on the machine.", 21 | "GET /session/:sessionId/ime/active_engine": "Get the name of the active IME engine.", 22 | "GET /session/:sessionId/ime/activated": "Indicates whether IME input is active at the moment (not if it's available).", 23 | "POST /session/:sessionId/ime/deactivate": "De-activates the currently-active IME engine.", 24 | "POST /session/:sessionId/ime/activate": "Make an engines that is available (appears on the listreturned by getAvailableEngines) active.", 25 | "POST /session/:sessionId/frame": "Change focus to another frame on the page.", 26 | "POST /session/:sessionId/window": "Change focus to another window.", 27 | "DELETE /session/:sessionId/window": "Close the current window.", 28 | "POST /session/:sessionId/window/:windowHandle/size": "Change the size of the specified window.", 29 | "GET /session/:sessionId/window/:windowHandle/size": "Get the size of the specified window.", 30 | "POST /session/:sessionId/window/:windowHandle/position": "Change the position of the specified window.", 31 | "GET /session/:sessionId/window/:windowHandle/position": "Get the position of the specified window.", 32 | "POST /session/:sessionId/window/:windowHandle/maximize": "Maximize the specified window if not already maximized.", 33 | "GET /session/:sessionId/cookie": "Retrieve all cookies visible to the current page.", 34 | "POST /session/:sessionId/cookie": "Set a cookie.", 35 | "DELETE /session/:sessionId/cookie": "Delete all cookies visible to the current page.", 36 | "DELETE /session/:sessionId/cookie/:name": "Delete the cookie with the given name.", 37 | "GET /session/:sessionId/source": "Get the current page source.", 38 | "GET /session/:sessionId/title": "Get the current page title.", 39 | "POST /session/:sessionId/element": "Search for an element on the page, starting from the document root.", 40 | "POST /session/:sessionId/elements": "Search for multiple elements on the page, starting from the document root.", 41 | "POST /session/:sessionId/element/active": "Get the element on the page that currently has focus.", 42 | "GET /session/:sessionId/element/:id": "Describe the identified element.", 43 | "POST /session/:sessionId/element/:id/element": "Search for an element on the page, starting from the identified element.", 44 | "POST /session/:sessionId/element/:id/elements": "Search for multiple elements on the page, starting from the identified element.", 45 | "POST /session/:sessionId/element/:id/click": "Click on an element.", 46 | "POST /session/:sessionId/element/:id/submit": "Submit a FORM element.", 47 | "GET /session/:sessionId/element/:id/text": "Returns the visible text for the element.", 48 | "POST /session/:sessionId/element/:id/value": "Send a sequence of key strokes to an element.", 49 | "POST /session/:sessionId/keys": "Send a sequence of key strokes to the active element.", 50 | "GET /session/:sessionId/element/:id/name": "Query for an element's tag name.", 51 | "POST /session/:sessionId/element/:id/clear": "Clear a TEXTAREA or text INPUT element's value.", 52 | "GET /session/:sessionId/element/:id/selected": "Determine if an OPTION element, or an INPUT element of type checkbox or radiobutton is currently selected.", 53 | "GET /session/:sessionId/element/:id/enabled": "Determine if an element is currently enabled.", 54 | "GET /session/:sessionId/element/:id/attribute/:name": "Get the value of an element's attribute.", 55 | "GET /session/:sessionId/element/:id/equals/:other": "Test if two element IDs refer to the same DOM element.", 56 | "GET /session/:sessionId/element/:id/displayed": "Determine if an element is currently displayed.", 57 | "GET /session/:sessionId/element/:id/location": "Determine an element's location on the page.", 58 | "GET /session/:sessionId/element/:id/location_in_view": "Determine an element's location on the screen once it has been scrolled into view.", 59 | "GET /session/:sessionId/element/:id/size": "Determine an element's size in pixels.", 60 | "GET /session/:sessionId/element/:id/css/:propertyName": "Query the value of an element's computed CSS property.", 61 | "GET /session/:sessionId/orientation": "Get the current browser orientation.", 62 | "POST /session/:sessionId/orientation": "Set the browser orientation.", 63 | "GET /session/:sessionId/alert_text": "Gets the text of the currently displayed JavaScript alert(), confirm(), or prompt() dialog.", 64 | "POST /session/:sessionId/alert_text": "Sends keystrokes to a JavaScript prompt() dialog.", 65 | "POST /session/:sessionId/accept_alert": "Accepts the currently displayed alert dialog.", 66 | "POST /session/:sessionId/dismiss_alert": "Dismisses the currently displayed alert dialog.", 67 | "POST /session/:sessionId/moveto": "Move the mouse by an offset of the specificed element.", 68 | "POST /session/:sessionId/click": "Click any mouse button (at the coordinates set by the last moveto command).", 69 | "POST /session/:sessionId/buttondown": "Click and hold the left mouse button (at the coordinates set by the last moveto command).", 70 | "POST /session/:sessionId/buttonup": "Releases the mouse button previously held (where the mouse is currently at).", 71 | "POST /session/:sessionId/doubleclick": "Double-clicks at the current mouse coordinates (set by moveto).", 72 | "POST /session/:sessionId/touch/click": "Single tap on the touch enabled device.", 73 | "POST /session/:sessionId/touch/down": "Finger down on the screen.", 74 | "POST /session/:sessionId/touch/up": "Finger up on the screen.", 75 | "POST /session/:sessionId/touch/move": "Finger move on the screen.", 76 | "POST /session/:sessionId/touch/scroll": "Scroll on the touch screen using finger based motion events.", 77 | "POST /session/:sessionId/touch/doubleclick": "Double tap on the touch screen using finger motion events.", 78 | "POST /session/:sessionId/touch/longclick": "Long press on the touch screen using finger motion events.", 79 | "POST /session/:sessionId/touch/flick": "Flick on the touch screen using finger motion events.", 80 | "GET /session/:sessionId/location": "Get the current geo location.", 81 | "POST /session/:sessionId/location": "Set the current geo location.", 82 | "GET /session/:sessionId/local_storage": "Get all keys of the storage.", 83 | "POST /session/:sessionId/local_storage": "Set the storage item for the given key.", 84 | "DELETE /session/:sessionId/local_storage": "Clear the storage.", 85 | "GET /session/:sessionId/local_storage/key/:key": "Get the storage item for the given key.", 86 | "DELETE /session/:sessionId/local_storage/key/:key": "Remove the storage item for the given key.", 87 | "GET /session/:sessionId/local_storage/size": "Get the number of items in the storage.", 88 | "GET /session/:sessionId/session_storage": "Get all keys of the storage.", 89 | "POST /session/:sessionId/session_storage": "Set the storage item for the given key.", 90 | "DELETE /session/:sessionId/session_storage": "Clear the storage.", 91 | "GET /session/:sessionId/session_storage/key/:key": "Get the storage item for the given key.", 92 | "DELETE /session/:sessionId/session_storage/key/:key": "Remove the storage item for the given key.", 93 | "GET /session/:sessionId/session_storage/size": "Get the number of items in the storage.", 94 | "POST /session/:sessionId/log": "Get the log for a given log type.", 95 | "GET /session/:sessionId/log/types": "Get available log types.", 96 | "GET /session/:sessionId/application_cache/status": "Get the status of the html5 application cache.", 97 | "GET /session/:sessionId/context": "Get the current context (mjsonWire).", 98 | "POST /session/:sessionId/context": "Set the current context (mjsonWire).", 99 | "GET /session/:sessionId/contexts": "Get a list of the available contexts (mjsonWire).", 100 | "POST /session/:sessionId/touch/perform": "Perform touch action (mjsonWire).", 101 | "POST /session/:sessionId/touch/multi/perform": "Perform multitouch action (mjsonWire).", 102 | "POST /session/:sessionId/appium/device/shake": "Shake device (mjsonWire).", 103 | "POST /session/:sessionId/appium/device/lock": "Lock device (mjsonWire).", 104 | "POST /session/:sessionId/appium/device/keyevent": "Send key event to device (DEPRECATED) (mjsonWire).", 105 | "POST /session/:sessionId/appium/device/press_keycode": "Send key event to device (mjsonWire).", 106 | "POST /session/:sessionId/appium/device/rotate": "Rotate device (mjsonWire).", 107 | "GET /session/:sessionId/appium/device/current_activity": "Get current activity (mjsonWire).", 108 | "GET /session/:sessionId/appium/device/current_package": "Get current package (mjsonWire).", 109 | "POST /session/:sessionId/appium/device/install_app": "Install app (mjsonWire).", 110 | "POST /session/:sessionId/appium/device/remove_app": "Remove app (mjsonWire).", 111 | "POST /session/:sessionId/appium/device/app_installed": "Check if the app is installed (mjsonWire).", 112 | "POST /session/:sessionId/appium/device/push_file": "Push file to device (mjsonWire).", 113 | "POST /session/:sessionId/appium/device/pull_file": "Pull file from device (mjsonWire).", 114 | "POST /session/:sessionId/appium/device/pull_folder": "Pull folder from device (mjsonWire).", 115 | "POST /session/:sessionId/appium/device/toggle_airplane_mode": "Toggle airplane mode (mjsonWire).", 116 | "POST /session/:sessionId/appium/device/toggle_wifi": "Toggle wifi (mjsonWire).", 117 | "POST /session/:sessionId/appium/device/toggle_location_services": "Toggle location services (mjsonWire).", 118 | "POST /session/:sessionId/appium/device/toggle_data": "Toggle data (mjsonWire).", 119 | "POST /session/:sessionId/appium/device/start_activity": "Start an Android activity (mjsonWire).", 120 | "POST /session/:sessionId/appium/app/launch": "Launch app (mjsonWire).", 121 | "POST /session/:sessionId/appium/app/close": "Close app (mjsonWire).", 122 | "POST /session/:sessionId/appium/app/reset": "Reset app (mjsonWire).", 123 | "POST /session/:sessionId/appium/app/background": "Background app (mjsonWire).", 124 | "POST /session/:sessionId/appium/app/end_test_coverage": "End test coverage (mjsonWire).", 125 | "POST /session/:sessionId/appium/app/complex_find": "Find within app (mjsonWire).", 126 | "POST /session/:sessionId/appium/app/strings": "Retrieve app strings (mjsonWire).", 127 | "POST /session/:sessionId/appium/element/:elementId?/value": "Set element immediate value (mjsonWire).", 128 | "GET /session/:sessionId/network_connection": "Get appium selendroid network connection type (mjsonWire).", 129 | "POST /session/:sessionId/network_connection": "Set appium selendroid network connection type (mjsonWire).", 130 | "POST /session/:sessionId/appium/device/hide_keyboard": "Hide keyboard (mjsonWire).", 131 | "POST /session/:sessionId/appium/device/open_notifications": "Open Notifications (mjsonWire).", 132 | "POST /session/:sessionId/appium/device/finger_print": "Send fingerprint (mjsonWire).", 133 | "POST /session/:sessionId/appium/device/send_sms": "Send sms to Android emulator (mjsonWire).", 134 | "POST /session/:sessionId/appium/device/gsm_call": "Send GSM call to Android emulator (mjsonWire).", 135 | "POST /session/:sessionId/appium/device/gsm_signal": "Set GSM signal strenght on Android emulator (mjsonWire).", 136 | "POST /session/:sessionId/appium/device/gsm_voice": "Set GSM state fingerprint (mjsonWire).", 137 | "POST /session/:sessionId/appium/device/power_capacity": "Set battery percent on Android emulator (mjsonWire).", 138 | "POST /session/:sessionId/appium/device/power_ac": "Set state of power charger on Android emulator(mjsonWire).", 139 | "POST /session/:sessionId/appium/device/network_speed": "Set Android emulator network speed (mjsonWire).", 140 | "POST /session/:sessionId/simulator/touch_id": "Simulate iOS touchID (mjsonWire)" 141 | } 142 | -------------------------------------------------------------------------------- /src/AppiumDriver.php: -------------------------------------------------------------------------------- 1 | getModule('AppiumDriver')->AppiumDriver->getKeyboard()->sendKeys('hello, AppiumDriver'); 33 | * ``` 34 | */ 35 | class AppiumDriver extends CodeceptionModule implements 36 | MultiSessionInterface, 37 | ScreenshotSaver, 38 | RequiresPackage 39 | { 40 | use BaseCommands; 41 | use Touch; 42 | use Key; 43 | use Elm; 44 | use Util; 45 | 46 | protected $requiredFields = ['host']; 47 | protected $config 48 | = [ 49 | 'host' => '127.0.0.1', 50 | 'port' => '4723', 51 | 'resetAfterSuite' => true, 52 | 'resetAfterCest' => true, 53 | 'resetAfterStep' => false, 54 | 'resetAfterTest' => false, 55 | 'capabilities' => [], 56 | 'connection_timeout' => null, 57 | 'request_timeout' => null, 58 | 'http_proxy' => null, 59 | 'http_proxy_port' => null, 60 | 'ssl_proxy' => null, 61 | 'ssl_proxy_port' => null, 62 | 'debug_log_entries' => 15, 63 | ]; 64 | 65 | protected $wd_host; 66 | /** @var \PHPUnit_Extensions_Selenium2TestCase_URL */ 67 | protected $selenium_url; 68 | protected $capabilities; 69 | protected $connectionTimeoutInMs; 70 | protected $requestTimeoutInMs; 71 | protected $test; 72 | protected $sessionSnapshots = []; 73 | protected $sessions = []; 74 | protected $httpProxy; 75 | protected $httpProxyPort; 76 | protected $sslProxy; 77 | protected $sslProxyPort; 78 | 79 | 80 | /** 81 | * @var AppiumRemoteDriver 82 | */ 83 | public $AppiumDriver; 84 | /** 85 | * @var Session 86 | */ 87 | public $AppiumSession; 88 | 89 | /** 90 | * @var array 91 | */ 92 | protected $classes = []; 93 | 94 | /** 95 | * @return array 96 | */ 97 | public function _requires() 98 | { 99 | return []; 100 | } 101 | 102 | /** 103 | * 104 | */ 105 | public function _initialize() 106 | { 107 | $this->wd_host = sprintf('http://%s:%s/wd/hub', $this->config['host'], $this->config['port']); 108 | $this->selenium_url = new \PHPUnit_Extensions_Selenium2TestCase_URL(sprintf('http://%s:%s', $this->config['host'], $this->config['port'])); 109 | $this->capabilities = $this->config['capabilities']; 110 | $this->outputCli("Snapshot Saved session snapshot"); 111 | 112 | $this->connectionTimeoutInMs = $this->config['connection_timeout'] * 1000; 113 | $this->requestTimeoutInMs = $this->config['request_timeout'] * 1000; 114 | try { 115 | if (!empty($this->config['dummyRemote'])) { 116 | $this->AppiumDriver = new Dummy(); 117 | } else { 118 | $this->AppiumDriver = new AppiumRemoteDriver($this->selenium_url, $this->connectionTimeoutInMs); 119 | $this->AppiumSession = $this->AppiumDriver->startSession($this->capabilities, $this->selenium_url); 120 | } 121 | 122 | $this->sessions[] = $this->_backupSession(); 123 | } catch (\Exception $e) { 124 | throw new ConnectionException( 125 | $e->getMessage() . "\n \nPlease make sure that Selenium Server or PhantomJS is running." 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * @param TestInterface $test 132 | */ 133 | public function _before(TestInterface $test) 134 | { 135 | $file = $test->getMetadata()->getFilename(); 136 | $class = $this->getClassNames($file)[0]; 137 | $classMd5 = $class; 138 | 139 | if ($this->config['resetAfterCest'] && !key_exists($classMd5, $this->classes)) { 140 | $this->classes[$classMd5] = $class; 141 | 142 | if (count($this->classes) > 1) { 143 | $this->outputCli('Cleaning appium: before ' . $class); 144 | $this->cleanAppiumDriver(); 145 | } 146 | } 147 | 148 | if (!isset($this->AppiumSession)) { 149 | $this->_initialize(); 150 | } 151 | $test->getMetadata()->setCurrent([ 152 | 'capabilities' => $this->config['capabilities'], 153 | ]); 154 | } 155 | 156 | /** 157 | * @param TestInterface $test 158 | */ 159 | public function _after(TestInterface $test) 160 | { 161 | 162 | if ($this->config['resetAfterTest']) { 163 | $this->outputCli('Cleaning appium: after ' . $test->getMetadata()->getName()); 164 | $this->cleanAppiumDriver(); 165 | 166 | return; 167 | } 168 | } 169 | 170 | /** 171 | * @param Step $step 172 | */ 173 | public function _afterStep(Step $step) 174 | { 175 | // this is just to make sure AppiumDriver is cleared after suite 176 | if ($this->config['resetAfterStep']) { 177 | $this->outputCli('Cleaning appium: after ' . $step->getAction()); 178 | $this->cleanAppiumDriver(); 179 | } 180 | } 181 | 182 | /** 183 | * 184 | */ 185 | public function _afterSuite() 186 | { 187 | // this is just to make sure AppiumDriver is cleared after suite 188 | if ($this->config['resetAfterSuite']) { 189 | $this->outputCli('Cleaning appium: after suite'); 190 | $this->cleanAppiumDriver(); 191 | } 192 | } 193 | 194 | /** 195 | * @param TestInterface $test 196 | * @param \Exception $fail 197 | */ 198 | public function _failed(TestInterface $test, $fail) 199 | { 200 | // todo from appium logs 201 | //$this->debugAppiumDriverLogs(); 202 | $filename = preg_replace('~\W~', '.', Descriptor::getTestSignature($test)); 203 | $outputDir = codecept_output_dir(); 204 | $this->_saveScreenshot($report = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png'); 205 | $test->getMetadata()->addReport('png', $report); 206 | $this->debug("Screenshot is saved into '$outputDir' dir"); 207 | } 208 | 209 | /** 210 | * Print out latest Selenium Logs in debug mode 211 | */ 212 | public function debugAppiumDriverLogs() 213 | { 214 | // todo implement 215 | } 216 | 217 | /** 218 | * Turns an array of log entries into a human-readable string. 219 | * Each log entry is an array with the keys "timestamp", "level", and "message". 220 | * See https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Entry_JSON_Object 221 | * 222 | * @param array $logEntries 223 | * 224 | * @return string 225 | */ 226 | protected function formatLogEntries(array $logEntries) 227 | { 228 | $formattedLogs = ''; 229 | 230 | foreach ($logEntries as $logEntry) { 231 | // Timestamp is in milliseconds, but date() requires seconds. 232 | $time = date('H:i:s', $logEntry['timestamp'] / 1000) . 233 | // Append the milliseconds to the end of the time string 234 | '.' . ($logEntry['timestamp'] % 1000); 235 | $formattedLogs .= "{$time} {$logEntry['level']} - {$logEntry['message']}\n"; 236 | } 237 | 238 | return $formattedLogs; 239 | } 240 | 241 | /** 242 | * clean appium session 243 | */ 244 | protected function cleanAppiumDriver() 245 | { 246 | foreach ($this->sessions as $session) { 247 | $this->_loadSession($session); 248 | try { 249 | $this->AppiumSession->stop(); 250 | } catch (\Exception $e) { 251 | // Session already closed so nothing to do 252 | } 253 | unset($this->AppiumSession); 254 | } 255 | $this->sessions = []; 256 | } 257 | 258 | /** 259 | * @return array|null 260 | */ 261 | protected function getProxy() 262 | { 263 | $proxyConfig = []; 264 | if ($this->config['http_proxy']) { 265 | $proxyConfig['httpProxy'] = $this->config['http_proxy']; 266 | if ($this->config['http_proxy_port']) { 267 | $proxyConfig['httpProxy'] .= ':' . $this->config['http_proxy_port']; 268 | } 269 | } 270 | if ($this->config['ssl_proxy']) { 271 | $proxyConfig['sslProxy'] = $this->config['ssl_proxy']; 272 | if ($this->config['ssl_proxy_port']) { 273 | $proxyConfig['sslProxy'] .= ':' . $this->config['ssl_proxy_port']; 274 | } 275 | } 276 | if (!empty($proxyConfig)) { 277 | $proxyConfig['proxyType'] = 'manual'; 278 | 279 | return $proxyConfig; 280 | } 281 | 282 | return null; 283 | } 284 | 285 | /** 286 | * @return string|null 287 | */ 288 | public function getDeviceName() 289 | { 290 | $cap = $this->config['capabilities']; 291 | return isset($cap['deviceName']) ? $cap['deviceName'] : ''; 292 | } 293 | 294 | /** 295 | * 296 | */ 297 | public function _initializeSession() 298 | { 299 | $this->AppiumDriver = new AppiumRemoteDriver($this->selenium_url, $this->connectionTimeoutInMs); 300 | $this->AppiumSession = $this->AppiumDriver->startSession($this->capabilities, $this->selenium_url); 301 | $this->sessions[] = $this->_backupSession(); 302 | } 303 | 304 | /** 305 | * @param $session 306 | */ 307 | public function _loadSession($session) 308 | { 309 | $this->AppiumSession = $session; 310 | } 311 | 312 | /** 313 | * @param $filename 314 | */ 315 | public function _saveScreenshot($filename) 316 | { 317 | if ($this->AppiumSession !== null) { 318 | $this->takeScreenshotAndSave($filename); 319 | } else { 320 | codecept_debug('AppiumDriver::_saveScreenshot method has been called when AppiumDriver is not set'); 321 | codecept_debug(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); 322 | } 323 | } 324 | 325 | /** 326 | * @return Session 327 | */ 328 | public function _backupSession() 329 | { 330 | return $this->AppiumSession; 331 | } 332 | 333 | /** 334 | * @param Session $AppiumSession 335 | */ 336 | public function _closeSession($AppiumSession = null) 337 | { 338 | $keys = array_keys($this->sessions, $AppiumSession, true); 339 | $key = array_shift($keys); 340 | try { 341 | $AppiumSession->stop(); 342 | } catch (\Exception $e) { 343 | // Session already closed so nothing to do 344 | } 345 | unset($this->sessions[$key]); 346 | } 347 | 348 | /** 349 | * @return \Appium\Remote\AppiumRemoteDriver 350 | */ 351 | public function getDriver() 352 | { 353 | return $this->AppiumDriver; 354 | } 355 | 356 | /** 357 | * @return \PHPUnit_Extensions_Selenium2TestCase_URL 358 | */ 359 | public function getSessionUrl() 360 | { 361 | return $this->getSession()->getSessionUrl(); 362 | } 363 | 364 | /** 365 | * @return \Appium\TestCase\Session 366 | */ 367 | public function getSession() 368 | { 369 | return $this->AppiumSession; 370 | } 371 | 372 | /** 373 | * get class names from php file 374 | * 375 | * @param $file 376 | * 377 | * @return array 378 | */ 379 | protected function getClassNames($file) 380 | { 381 | $php_code = file_get_contents($file); 382 | $classes = []; 383 | $namespace = ""; 384 | $tokens = token_get_all($php_code); 385 | $count = count($tokens); 386 | 387 | for ($i = 0; $i < $count; $i++) { 388 | if ($tokens[$i][0] === T_NAMESPACE) { 389 | for ($j = $i + 1; $j < $count; ++$j) { 390 | if ($tokens[$j][0] === T_STRING) { 391 | $namespace .= "\\" . $tokens[$j][1]; 392 | } elseif ($tokens[$j] === '{' or $tokens[$j] === ';') { 393 | break; 394 | } 395 | } 396 | } 397 | if ($tokens[$i][0] === T_CLASS) { 398 | for ($j = $i + 1; $j < $count; ++$j) { 399 | if ($tokens[$j] === '{') { 400 | $classes[] = $namespace . "\\" . $tokens[$i + 2][1]; 401 | } 402 | } 403 | } 404 | } 405 | 406 | return $classes; 407 | } 408 | 409 | /** 410 | * print to cli 411 | * 412 | * @param $msg 413 | */ 414 | public function outputCli($msg) 415 | { 416 | $output = new Output([]); 417 | $output->writeln(''); 418 | $output->writeln($msg); 419 | $output->writeln(''); 420 | } 421 | 422 | //// ___ _ ___ __ __ ___ _ _ _____ ___ 423 | //// | __| | | __| \/ | __| \| |_ _/ __| 424 | //// | _|| |__| _|| |\/| | _|| .` | | | \__ \ 425 | //// |___|____|___|_| |_|___|_|\_| |_| |___/ 426 | //// 427 | 428 | /** 429 | * @return \Appium\TestCase\Element 430 | */ 431 | public function TestCaseElement() 432 | { 433 | return new Element($this->AppiumDriver, $this->getSessionUrl()); 434 | } 435 | 436 | 437 | //// ___ ___ __ __ __ __ _ _ _ ___ ___ 438 | //// / __/ _ \| \/ | \/ | /_\ | \| | \/ __| 439 | //// | (_| (_) | |\/| | |\/| |/ _ \| .` | |) \__ \ 440 | //// \___\___/|_| |_|_| |_/_/ \_\_|\_|___/|___/ 441 | //// 442 | 443 | const POST = 'POST'; 444 | const GET = 'GET'; 445 | const DEL = 'DELETE'; 446 | 447 | /** 448 | * @param $method 449 | * @param $command 450 | * @param $data 451 | * 452 | * @return mixed 453 | */ 454 | public function driverCommand($method = 'POST', $command, $data = []) 455 | { 456 | 457 | $url = $this->getSession()->getSessionUrl()->descend($command); 458 | 459 | /** @var \PHPUnit_Extensions_Selenium2TestCase_Response $response */ 460 | $response = $this->getDriver()->curl($method, $url, $data); 461 | 462 | return $response->getValue(); 463 | } 464 | 465 | /** 466 | * @param $method 467 | * @param $command 468 | * @param $data 469 | * 470 | * @return mixed 471 | */ 472 | public function driverCommandWithoutSession($method = 'POST', $command, $data = []) 473 | { 474 | 475 | $url = $this->getSession()->getSessionUrl()->descend($command); 476 | 477 | /** @var \PHPUnit_Extensions_Selenium2TestCase_Response $response */ 478 | $response = $this->getDriver()->curl($method, $url, $data); 479 | 480 | return $response->getValue(); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/Traits/BaseConstants.php: -------------------------------------------------------------------------------- 1 | symfonyStyle = $symfonyStyle; 81 | $jsonFile = ($jsonFile) ? $jsonFile : $this->defaultJsonFile; 82 | $this->jsonObject = json_decode(file_get_contents($jsonFile), true); 83 | } 84 | 85 | /** 86 | * Generate class and constants 87 | * 88 | */ 89 | public function generate() 90 | { 91 | $this->generateFullFile() 92 | ->createFunctions() 93 | ->createConstants() 94 | ->createConstantsFile() 95 | ->createClassFile() 96 | ->generateDocMd(); 97 | 98 | $this->symfonyStyle->success("You will find output files in {$this->classFileLocation}"); 99 | } 100 | 101 | public function itemList($arr) 102 | { 103 | $list = ''; 104 | foreach ($arr as $value) { 105 | $listItem = (is_array($value) ? $this->itemList($value) : $value); 106 | $list .= "- $listItem"; 107 | } 108 | $list .= ''; 109 | 110 | return $list; 111 | } 112 | 113 | public function generateDocMd() 114 | { 115 | $columns = ['Method Name', 'HTTP', 'Url/Desc', 'Payload']; 116 | $rows = []; 117 | foreach ($this->functionsArray as $function) { 118 | $params = ''; 119 | if ($function['payloadParams']) { 120 | $params = json_encode($function['payloadParams'], JSON_ERROR_UNSUPPORTED_TYPE); 121 | } 122 | 123 | 124 | $rows[] = [ 125 | $function['name'], 126 | $function['http_method'], 127 | $function['url'] . "
" . $function['desc'], 128 | $params, 129 | ]; 130 | } 131 | $t = new TextTable($columns, $rows); 132 | $t->maxlen = 400; 133 | //$t->setAlgin(['L', 'C', 'R']); 134 | 135 | $mdTable = $t->render(); 136 | 137 | $readMeFile = __DIR__ . '/../../APPIUM_CORE_FUNCTIONS.md'; 138 | $txt = file_get_contents($readMeFile); 139 | $txtNew = $this->replaceInStrWithDel($txt, '[comment]: # (core-function-comment)', $mdTable); 140 | file_put_contents($readMeFile, $txtNew); 141 | } 142 | 143 | public function replaceInStrWithDel($str, $del, $replace) 144 | { 145 | $txtArr = explode($del, $str); 146 | 147 | $newStr = join( 148 | "\n", 149 | [ 150 | trim($txtArr[0]), 151 | "$del\n\n" . $replace . "\n\n$del", 152 | trim($txtArr['2']), 153 | ] 154 | ); 155 | 156 | return $newStr; 157 | } 158 | 159 | public function httpMethod($str) 160 | { 161 | $isPost = stristr($str, 'POST /'); 162 | if ($isPost) { 163 | return 'POST'; 164 | } 165 | $isGET = stristr($str, 'GET /'); 166 | if ($isGET) { 167 | return 'GET'; 168 | } 169 | 170 | $isDELETE = stristr($str, 'DELETE /'); 171 | if ($isDELETE) { 172 | return 'DELETE'; 173 | } 174 | 175 | return 'NA'; 176 | } 177 | 178 | public function getCommandNameOfUrl($url, $verb = '') 179 | { 180 | $urlExt = str_ireplace(['get', 'post', 'delete'], '', $url); 181 | $urlExt = str_ireplace('/wd/hub/session', '', $urlExt); 182 | $urlExt = preg_replace('#(:\w+)#', '', $urlExt); 183 | $urlExt = preg_replace('#\/\/#', '/', $urlExt); 184 | $urlExt = trim($urlExt, '/'); 185 | $urlExt = trim($urlExt); 186 | 187 | $urlExtArr = explode('/', $urlExt); 188 | $urlExtArr = array_unique($urlExtArr); 189 | $urlExtArrLast = array_slice($urlExtArr, -2, 2); 190 | 191 | 192 | $urlStr = join('_', $urlExtArrLast); 193 | $urlStr = strtolower($verb) . '_' . $urlStr; 194 | 195 | 196 | return $urlStr; 197 | } 198 | 199 | public function toCamelCase($string, $del, $capitalizeFirstCharacter = false) 200 | { 201 | 202 | $str = str_replace($del, '', ucwords($string, $del)); 203 | 204 | if (!$capitalizeFirstCharacter) { 205 | $str = lcfirst($str); 206 | } 207 | 208 | return $str; 209 | } 210 | 211 | 212 | protected function generateFullFile() 213 | { 214 | $arrCommands = []; 215 | 216 | $jsonWireObject = json_decode(file_get_contents($this->defaultJsonWireFile), true); 217 | $jsonRouteObject = json_decode(file_get_contents($this->defaultJsonRouteFile), true); 218 | $jsonRouteObjectOverRide = json_decode(file_get_contents($this->defaultJsonRouteOverrideFile), true); 219 | $extraRouteObject = json_decode(file_get_contents($this->defaultJsonExtraFile), true); 220 | 221 | $jsonRouteObjectSm = []; 222 | $jsonWireObjectSm = []; 223 | 224 | foreach ($jsonRouteObject as $key => $value) { 225 | $jsonRouteObjectSm[strtolower($key)] = $value; 226 | } 227 | 228 | foreach ($extraRouteObject as $key => $value) { 229 | $jsonRouteObjectSm[strtolower($key)] = $value; 230 | } 231 | 232 | // convert route file to method route so we can match it with jsonwire full 233 | $jsonRouteObjectSmVerb = []; 234 | foreach ($jsonRouteObjectSm as $key => $value) { 235 | foreach ($value as $verb => $verbValue) { 236 | $verbValue['http_method'] = $verb; 237 | $verbValue['src'] = 'route.json'; 238 | $verbValue['link'] = ['https://github.com/appium/appium-base-driver/blob/master/lib/protocol/routes.js']; 239 | $jsonRouteObjectSmVerb[strtolower($verb) . ' ' . strtolower($key)] = $verbValue; 240 | } 241 | } 242 | 243 | // override 244 | foreach ($jsonRouteObjectOverRide as $key => $value) { 245 | foreach ($value as $verb => $verbValue) { 246 | $orgArr = ($jsonRouteObjectSmVerb[strtolower($verb) . ' ' . strtolower($key)]); 247 | 248 | $jsonRouteObjectSmVerb[strtolower($verb) . ' ' . strtolower($key)] = array_merge($orgArr, $verbValue); 249 | } 250 | } 251 | 252 | foreach ($jsonWireObject as $key => $commandDesc) { 253 | preg_match('#\w+ #', $key, $match); 254 | $httpMethod = trim($match[0]); 255 | $keyUrl = str_replace(' ', ' /wd/hub', $key); 256 | $jsonWireObjectSm[strtolower($keyUrl)] = [ 257 | 'desc' => $commandDesc, 258 | 'http_method' => $httpMethod, 259 | 'src' => 'jsonwire-full', 260 | 'link' => ['https://github.com/admc/wd/blob/master/doc/jsonwire-full-mapping.md'], 261 | ]; 262 | } 263 | 264 | 265 | // add not founds 266 | foreach ($jsonRouteObjectSmVerb as $key => $value) { 267 | $cmdDetail = $jsonWireObjectSm[$key] ?? false; 268 | if (!$cmdDetail) { 269 | $jsonWireObjectSm[$key] = [ 270 | 'desc' => $key, 271 | 'http_method' => $value['http_method'], 272 | ]; 273 | } 274 | } 275 | 276 | // foreach ($jsonWireObjectSm as $key => $commandDesc) { 277 | // $cmdDetail = $jsonRouteObjectSmVerb[$key] ?? false; 278 | // 279 | // if (!$cmdDetail) { 280 | // add extra Commands 281 | // $jsonWireObjectSm[$key] = [ 282 | // 'desc' => $commandDesc, 283 | // 'http_method' => $httpMethod 284 | // ]; 285 | // } 286 | // } 287 | 288 | foreach ($jsonWireObjectSm as $key => $commandDesc) { 289 | $cmd = $key; 290 | $urlNoVerb = str_ireplace(['post', 'get', 'delete',], '', $key); 291 | $url = str_ireplace(['/wd/hub', '/session/:sessionid'], '', $urlNoVerb); 292 | $url = trim($url); 293 | 294 | $cmdDetail = $jsonRouteObjectSmVerb[$key] ?? false; 295 | 296 | if ($cmdDetail) { 297 | if (!isset($cmdDetail['command'])) { 298 | $cmdDetail['command'] = $this->toCamelCase($this->getCommandNameOfUrl($cmd, 299 | $commandDesc['http_method']), '_'); 300 | } 301 | preg_match_all('#:(\w+)#', $url, $matches); 302 | $urlParams = array_unique($matches[1]); 303 | 304 | // remove validate 305 | $payload = []; 306 | 307 | if (!empty($cmdDetail['payloadParams']['required'])) { 308 | $payload['required'] = $cmdDetail['payloadParams']['required']; 309 | } 310 | 311 | if (!empty($cmdDetail['payloadParams']['optional'])) { 312 | $payload['optional'] = $cmdDetail['payloadParams']['optional']; 313 | } 314 | 315 | $arrCommands[$key] = [ 316 | 'url' => $url, 317 | 'wdUrl' => trim($urlNoVerb), 318 | 'name' => $cmdDetail['command'], 319 | 'desc' => $cmdDetail['desc'] ?? $commandDesc['desc'], 320 | 'http_method' => $this->httpMethod($key), 321 | 'uriParams' => $urlParams ?? [], 322 | 'payloadParams' => $payload, 323 | 'src' => $cmdDetail['src'] ?? '', 324 | 'note' => $cmdDetail['note'] ?? '', 325 | 'link' => $cmdDetail['link'] ?? [], 326 | ]; 327 | } 328 | } 329 | 330 | file_put_contents($this->defaultJsonFile, 331 | json_encode($arrCommands, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 332 | 333 | $this->functionsArray = $arrCommands; 334 | 335 | return $this; 336 | } 337 | 338 | /** 339 | * Create functions 340 | * 341 | * @return $this 342 | * 343 | */ 344 | protected function createFunctions() 345 | { 346 | if (empty($this->functionsArray)) { 347 | echo "Functions not found"; 348 | die(1); 349 | } 350 | 351 | foreach ($this->functionsArray as $key => $function) { 352 | $this->addConstant(strtoupper($function['name']), $function['url']); 353 | } 354 | 355 | return $this; 356 | } 357 | 358 | /** 359 | * Create constants from template 360 | * 361 | * @return $this 362 | */ 363 | protected function createConstants() 364 | { 365 | $this->constantsOutput = str_replace('{{constants}}', $this->constants, $this->constantsTemplate); 366 | 367 | return $this; 368 | } 369 | 370 | /** 371 | * Save class output in the file 372 | * 373 | * @return $this 374 | */ 375 | protected function createClassFile() 376 | { 377 | foreach ($this->functionsArray as $key => $function) { 378 | if (!empty($function['url'])) { 379 | $this->writeFunction($function); 380 | } 381 | $this->classOutput .= ""; 382 | } 383 | 384 | $this->classOutput = str_replace('{{functions}}', $this->classOutput, $this->classTemplate); 385 | 386 | file_put_contents($this->classFileLocation . '/' . $this->outputFileName . '.php', $this->classOutput); 387 | 388 | return $this; 389 | } 390 | 391 | /** 392 | * Write function 393 | * 394 | * @param $function 395 | * 396 | * @return string 397 | * 398 | */ 399 | protected function writeFunction($function) 400 | { 401 | // Skip the routes without the command 402 | if (!isset($function['url'])) { 403 | return ""; 404 | } 405 | 406 | $routeParamsString = ''; 407 | $optionsAnnotations = "\n\t/**\n"; 408 | $optionsAnnotations .= "\t* " . $function['name'] . "\n\t*\n"; 409 | 410 | $allOptions = $function['payloadParams']; 411 | 412 | $routeParams = $function['uriParams']; 413 | 414 | if ($routeParams) { 415 | foreach ($routeParams as $key => $param) { 416 | $routeParamsString .= ($key) ? ", " : ""; 417 | $routeParamsString .= "$" . $param; 418 | } 419 | } 420 | 421 | $optionsAnnotations .= ($function['desc']) ? "\t* {$function['desc']}\n" : ""; 422 | $optionsAnnotations .= ($function['note']) ? "\t* @note {$function['note']}\n" : ""; 423 | 424 | foreach ($function['link'] as $link) { 425 | $optionsAnnotations .= "\t* @link {$link}\n"; 426 | } 427 | 428 | $optionsAnnotations .= ($function['src']) ? "\t* @source {$function['src']}\n" : ""; 429 | $optionsAnnotations .= ($allOptions) ? "\t* @param array \$data\n" : ""; 430 | $optionsAnnotations .= ($allOptions) ? "\t* @options " . json_encode($allOptions) . "\n\t*\n" : ""; 431 | $optionsAnnotations .= ($allOptions) ? "\t* @return mixed\n" : ""; 432 | $options_ = ($allOptions) ? "\$data" : ""; 433 | $routeParamsString = ($allOptions && $routeParamsString) ? ", " . $routeParamsString : $routeParamsString; 434 | $this->classOutput .= $optionsAnnotations . "\t*\n\t**/\n"; 435 | $this->classOutput .= "\tpublic function " . $function['name'] . "(" . $options_ . "" . $routeParamsString . "){\n"; 436 | 437 | $this->classOutput .= "\t\t" . $this->getCommand($function['url'], $function['http_method'], 438 | $options_, $function['uriParams']) . "\n\t}"; 439 | $this->classArray[$function['name']] = $function['name']; 440 | 441 | return $function['name']; 442 | } 443 | 444 | /** 445 | * Get the command for the function 446 | * 447 | * @param $url 448 | * @param $type 449 | * @param $data 450 | * @param $routeParams 451 | * 452 | * @return string 453 | */ 454 | protected function getCommand($url, $type, $data, $routeParams) 455 | { 456 | $routeParamsString = ""; 457 | $data = ($data) ? ", \$data" : ''; 458 | $urlString = "'$url'"; 459 | if ($routeParams) { 460 | $urlString = "\$url"; 461 | $routeParamsString .= "\t\$url = '" . $url . "';\n\t\t"; 462 | foreach ($routeParams as $param) { 463 | $routeParamsString .= "\t\$url = str_ireplace(':" . $param . "', $" . $param . ", \$url );\n\t\t"; 464 | } 465 | } 466 | $command = $routeParamsString . "\treturn \$this->driverCommand(" . $this->constantsOutputFileName . "::$" . $type . ", " . $urlString . $data . ");"; 467 | 468 | return $command; 469 | } 470 | 471 | /** 472 | * Save class output in the file 473 | * 474 | * @return $this 475 | */ 476 | protected function createConstantsFile() 477 | { 478 | file_put_contents($this->classFileLocation . '/' . $this->constantsOutputFileName . '.php', 479 | $this->constantsOutput); 480 | 481 | return $this; 482 | } 483 | 484 | /** 485 | * Inspect url 486 | * 487 | * @param string $name 488 | * @param string $value 489 | * 490 | * @return mixed 491 | * 492 | */ 493 | protected function addConstant($name, $value) 494 | { 495 | $nameSm = strtolower($name); 496 | 497 | if (!isset($this->constantsArray[$nameSm])) { 498 | $this->constants .= "\n\t/** @var string */\n\tpublic static \$" . $name . " = '" . $value . "';\n"; 499 | $this->constantsArray[$nameSm] = $value; 500 | } else { 501 | echo "Duplicate key constants :" . $name . ' -- ' . $value . "\n"; 502 | } 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /cmd/Parser/Json/AppiumCommandRoute.json: -------------------------------------------------------------------------------- 1 | { 2 | "/wd/hub/status": { 3 | "GET": { 4 | "command": "getStatus" 5 | } 6 | }, 7 | "/wd/hub/session": { 8 | "POST": { 9 | "command": "createSession", 10 | "payloadParams": { 11 | "optional": [ 12 | "desiredCapabilities", 13 | "requiredCapabilities", 14 | "capabilities" 15 | ] 16 | } 17 | } 18 | }, 19 | "/wd/hub/sessions": { 20 | "GET": { 21 | "command": "getSessions" 22 | } 23 | }, 24 | "/wd/hub/session/:sessionId": { 25 | "GET": { 26 | "command": "getSession" 27 | }, 28 | "DELETE": { 29 | "command": "deleteSession" 30 | } 31 | }, 32 | "/wd/hub/session/:sessionId/timeouts": { 33 | "GET": { 34 | "command": "getTimeouts" 35 | }, 36 | "POST": { 37 | "command": "timeouts", 38 | "payloadParams": { 39 | "optional": [ 40 | "type", 41 | "ms", 42 | "script", 43 | "pageLoad", 44 | "implicit" 45 | ] 46 | } 47 | } 48 | }, 49 | "/wd/hub/session/:sessionId/timeouts/async_script": { 50 | "POST": { 51 | "command": "asyncScriptTimeout", 52 | "payloadParams": { 53 | "required": [ 54 | "ms" 55 | ] 56 | } 57 | } 58 | }, 59 | "/wd/hub/session/:sessionId/timeouts/implicit_wait": { 60 | "POST": { 61 | "command": "implicitWait", 62 | "payloadParams": { 63 | "required": [ 64 | "ms" 65 | ] 66 | } 67 | } 68 | }, 69 | "/wd/hub/session/:sessionId/window_handle": { 70 | "GET": { 71 | "command": "getWindowHandle" 72 | } 73 | }, 74 | "/wd/hub/session/:sessionId/window/handle": { 75 | "GET": { 76 | "command": "getWindowHandle" 77 | } 78 | }, 79 | "/wd/hub/session/:sessionId/window_handles": { 80 | "GET": { 81 | "command": "getWindowHandles" 82 | } 83 | }, 84 | "/wd/hub/session/:sessionId/window/handles": { 85 | "GET": { 86 | "command": "getWindowHandles" 87 | } 88 | }, 89 | "/wd/hub/session/:sessionId/url": { 90 | "GET": { 91 | "command": "getUrl" 92 | }, 93 | "POST": { 94 | "command": "setUrl", 95 | "payloadParams": { 96 | "required": [ 97 | "url" 98 | ] 99 | } 100 | } 101 | }, 102 | "/wd/hub/session/:sessionId/forward": { 103 | "POST": { 104 | "command": "forward" 105 | } 106 | }, 107 | "/wd/hub/session/:sessionId/back": { 108 | "POST": { 109 | "command": "back" 110 | } 111 | }, 112 | "/wd/hub/session/:sessionId/refresh": { 113 | "POST": { 114 | "command": "refresh" 115 | } 116 | }, 117 | "/wd/hub/session/:sessionId/execute": { 118 | "POST": { 119 | "command": "execute", 120 | "payloadParams": { 121 | "required": [ 122 | "script", 123 | "args" 124 | ] 125 | } 126 | } 127 | }, 128 | "/wd/hub/session/:sessionId/execute_async": { 129 | "POST": { 130 | "command": "executeAsync", 131 | "payloadParams": { 132 | "required": [ 133 | "script", 134 | "args" 135 | ] 136 | } 137 | } 138 | }, 139 | "/wd/hub/session/:sessionId/screenshot": { 140 | "GET": { 141 | "command": "getScreenshot" 142 | } 143 | }, 144 | "/wd/hub/session/:sessionId/ime/available_engines": { 145 | "GET": { 146 | "command": "availableIMEEngines" 147 | } 148 | }, 149 | "/wd/hub/session/:sessionId/ime/active_engine": { 150 | "GET": { 151 | "command": "getActiveIMEEngine" 152 | } 153 | }, 154 | "/wd/hub/session/:sessionId/ime/activated": { 155 | "GET": { 156 | "command": "isIMEActivated" 157 | } 158 | }, 159 | "/wd/hub/session/:sessionId/ime/deactivate": { 160 | "POST": { 161 | "command": "deactivateIMEEngine" 162 | } 163 | }, 164 | "/wd/hub/session/:sessionId/ime/activate": { 165 | "POST": { 166 | "command": "activateIMEEngine", 167 | "payloadParams": { 168 | "required": [ 169 | "engine" 170 | ] 171 | } 172 | } 173 | }, 174 | "/wd/hub/session/:sessionId/frame": { 175 | "POST": { 176 | "command": "setFrame", 177 | "payloadParams": { 178 | "required": [ 179 | "id" 180 | ] 181 | } 182 | } 183 | }, 184 | "/wd/hub/session/:sessionId/frame/parent": { 185 | "POST": {} 186 | }, 187 | "/wd/hub/session/:sessionId/window": { 188 | "GET": { 189 | "command": "getWindowHandle" 190 | }, 191 | "POST": { 192 | "command": "setWindow", 193 | "payloadParams": { 194 | "optional": [ 195 | "name", 196 | "handle" 197 | ] 198 | } 199 | }, 200 | "DELETE": { 201 | "command": "closeWindow" 202 | } 203 | }, 204 | "/wd/hub/session/:sessionId/window/:windowhandle/size": { 205 | "GET": { 206 | "command": "getWindowSize" 207 | }, 208 | "POST": {} 209 | }, 210 | "/wd/hub/session/:sessionId/window/:windowhandle/position": { 211 | "POST": {}, 212 | "GET": {} 213 | }, 214 | "/wd/hub/session/:sessionId/window/:windowhandle/maximize": { 215 | "POST": { 216 | "command": "maximizeWindow" 217 | } 218 | }, 219 | "/wd/hub/session/:sessionId/cookie": { 220 | "GET": { 221 | "command": "getCookies" 222 | }, 223 | "POST": { 224 | "command": "setCookie", 225 | "payloadParams": { 226 | "required": [ 227 | "cookie" 228 | ] 229 | } 230 | }, 231 | "DELETE": { 232 | "command": "deleteCookies" 233 | } 234 | }, 235 | "/wd/hub/session/:sessionId/cookie/:name": { 236 | "GET": { 237 | "command": "getCookie" 238 | }, 239 | "DELETE": { 240 | "command": "deleteCookie" 241 | } 242 | }, 243 | "/wd/hub/session/:sessionId/source": { 244 | "GET": { 245 | "command": "getPageSource" 246 | } 247 | }, 248 | "/wd/hub/session/:sessionId/title": { 249 | "GET": { 250 | "command": "title" 251 | } 252 | }, 253 | "/wd/hub/session/:sessionId/element": { 254 | "POST": { 255 | "command": "findElement", 256 | "payloadParams": { 257 | "required": [ 258 | "using", 259 | "value" 260 | ] 261 | } 262 | } 263 | }, 264 | "/wd/hub/session/:sessionId/elements": { 265 | "POST": { 266 | "command": "findElements", 267 | "payloadParams": { 268 | "required": [ 269 | "using", 270 | "value" 271 | ] 272 | } 273 | } 274 | }, 275 | "/wd/hub/session/:sessionId/element/active": { 276 | "GET": { 277 | "command": "active" 278 | }, 279 | "POST": { 280 | "command": "active" 281 | } 282 | }, 283 | "/wd/hub/session/:sessionId/element/:elementId": { 284 | "GET": {} 285 | }, 286 | "/wd/hub/session/:sessionId/element/:elementId/element": { 287 | "POST": { 288 | "command": "findElementFromElement", 289 | "payloadParams": { 290 | "required": [ 291 | "using", 292 | "value" 293 | ] 294 | } 295 | } 296 | }, 297 | "/wd/hub/session/:sessionId/element/:elementId/elements": { 298 | "POST": { 299 | "command": "findElementsFromElement", 300 | "payloadParams": { 301 | "required": [ 302 | "using", 303 | "value" 304 | ] 305 | } 306 | } 307 | }, 308 | "/wd/hub/session/:sessionId/element/:elementId/click": { 309 | "POST": { 310 | "command": "click" 311 | } 312 | }, 313 | "/wd/hub/session/:sessionId/element/:elementId/submit": { 314 | "POST": { 315 | "command": "submit" 316 | } 317 | }, 318 | "/wd/hub/session/:sessionId/element/:elementId/text": { 319 | "GET": { 320 | "command": "getText" 321 | } 322 | }, 323 | "/wd/hub/session/:sessionId/element/:elementId/value": { 324 | "POST": { 325 | "command": "setValue", 326 | "payloadParams": { 327 | "optional": [ 328 | "value", 329 | "text" 330 | ] 331 | } 332 | } 333 | }, 334 | "/wd/hub/session/:sessionId/keys": { 335 | "POST": { 336 | "command": "keys", 337 | "payloadParams": { 338 | "required": [ 339 | "value" 340 | ] 341 | } 342 | } 343 | }, 344 | "/wd/hub/session/:sessionId/element/:elementId/name": { 345 | "GET": { 346 | "command": "getName" 347 | } 348 | }, 349 | "/wd/hub/session/:sessionId/element/:elementId/clear": { 350 | "POST": { 351 | "command": "clear" 352 | } 353 | }, 354 | "/wd/hub/session/:sessionId/element/:elementId/selected": { 355 | "GET": { 356 | "command": "elementSelected" 357 | } 358 | }, 359 | "/wd/hub/session/:sessionId/element/:elementId/enabled": { 360 | "GET": { 361 | "command": "elementEnabled" 362 | } 363 | }, 364 | "/wd/hub/session/:sessionId/element/:elementId/attribute/:name": { 365 | "GET": { 366 | "command": "getAttribute" 367 | } 368 | }, 369 | "/wd/hub/session/:sessionId/element/:elementId/equals/:otherId": { 370 | "GET": { 371 | "command": "equalsElement" 372 | } 373 | }, 374 | "/wd/hub/session/:sessionId/element/:elementId/displayed": { 375 | "GET": { 376 | "command": "elementDisplayed" 377 | } 378 | }, 379 | "/wd/hub/session/:sessionId/element/:elementId/location": { 380 | "GET": { 381 | "command": "getLocation" 382 | } 383 | }, 384 | "/wd/hub/session/:sessionId/element/:elementId/location_in_view": { 385 | "GET": { 386 | "command": "getLocationInView" 387 | } 388 | }, 389 | "/wd/hub/session/:sessionId/element/:elementId/size": { 390 | "GET": { 391 | "command": "getSize" 392 | } 393 | }, 394 | "/wd/hub/session/:sessionId/element/:elementId/css/:propertyName": { 395 | "GET": { 396 | "command": "getCssProperty" 397 | } 398 | }, 399 | "/wd/hub/session/:sessionId/orientation": { 400 | "GET": { 401 | "command": "getOrientation" 402 | }, 403 | "POST": { 404 | "command": "setOrientation", 405 | "payloadParams": { 406 | "required": [ 407 | "orientation" 408 | ] 409 | } 410 | } 411 | }, 412 | "/wd/hub/session/:sessionId/rotation": { 413 | "GET": { 414 | "command": "getRotation" 415 | }, 416 | "POST": { 417 | "command": "setRotation", 418 | "payloadParams": { 419 | "required": [ 420 | "x", 421 | "y", 422 | "z" 423 | ] 424 | } 425 | } 426 | }, 427 | "/wd/hub/session/:sessionId/moveto": { 428 | "POST": { 429 | "command": "moveTo", 430 | "payloadParams": { 431 | "optional": [ 432 | "element", 433 | "xoffset", 434 | "yoffset" 435 | ] 436 | } 437 | } 438 | }, 439 | "/wd/hub/session/:sessionId/click": { 440 | "POST": { 441 | "command": "clickCurrent", 442 | "payloadParams": { 443 | "optional": [ 444 | "button" 445 | ] 446 | } 447 | } 448 | }, 449 | "/wd/hub/session/:sessionId/buttondown": { 450 | "POST": {} 451 | }, 452 | "/wd/hub/session/:sessionId/buttonup": { 453 | "POST": {} 454 | }, 455 | "/wd/hub/session/:sessionId/doubleclick": { 456 | "POST": {} 457 | }, 458 | "/wd/hub/session/:sessionId/touch/click": { 459 | "POST": { 460 | "command": "click", 461 | "payloadParams": { 462 | "required": [ 463 | "element" 464 | ] 465 | } 466 | } 467 | }, 468 | "/wd/hub/session/:sessionId/touch/down": { 469 | "POST": { 470 | "command": "touchDown", 471 | "payloadParams": { 472 | "required": [ 473 | "x", 474 | "y" 475 | ] 476 | } 477 | } 478 | }, 479 | "/wd/hub/session/:sessionId/touch/up": { 480 | "POST": { 481 | "command": "touchUp", 482 | "payloadParams": { 483 | "required": [ 484 | "x", 485 | "y" 486 | ] 487 | } 488 | } 489 | }, 490 | "/wd/hub/session/:sessionId/touch/move": { 491 | "POST": { 492 | "command": "touchMove", 493 | "payloadParams": { 494 | "required": [ 495 | "x", 496 | "y" 497 | ] 498 | } 499 | } 500 | }, 501 | "/wd/hub/session/:sessionId/touch/scroll": { 502 | "POST": {} 503 | }, 504 | "/wd/hub/session/:sessionId/touch/doubleclick": { 505 | "POST": {} 506 | }, 507 | "/wd/hub/session/:sessionId/actions": { 508 | "POST": { 509 | "command": "performActions", 510 | "payloadParams": { 511 | "required": [ 512 | "actions" 513 | ] 514 | } 515 | } 516 | }, 517 | "/wd/hub/session/:sessionId/touch/longclick": { 518 | "POST": { 519 | "command": "touchLongClick", 520 | "payloadParams": { 521 | "required": [ 522 | "elements" 523 | ] 524 | } 525 | } 526 | }, 527 | "/wd/hub/session/:sessionId/touch/flick": { 528 | "POST": { 529 | "command": "flick", 530 | "payloadParams": { 531 | "optional": [ 532 | "element", 533 | "xspeed", 534 | "yspeed", 535 | "xoffset", 536 | "yoffset", 537 | "speed" 538 | ] 539 | } 540 | } 541 | }, 542 | "/wd/hub/session/:sessionId/location": { 543 | "GET": { 544 | "command": "getGeoLocation" 545 | }, 546 | "POST": { 547 | "command": "setGeoLocation", 548 | "payloadParams": { 549 | "required": [ 550 | "location" 551 | ] 552 | } 553 | } 554 | }, 555 | "/wd/hub/session/:sessionId/local_storage": { 556 | "GET": {}, 557 | "POST": {}, 558 | "DELETE": {} 559 | }, 560 | "/wd/hub/session/:sessionId/local_storage/key/:key": { 561 | "GET": {}, 562 | "DELETE": {} 563 | }, 564 | "/wd/hub/session/:sessionId/local_storage/size": { 565 | "GET": {} 566 | }, 567 | "/wd/hub/session/:sessionId/session_storage": { 568 | "GET": {}, 569 | "POST": {}, 570 | "DELETE": {} 571 | }, 572 | "/wd/hub/session/:sessionId/session_storage/key/:key": { 573 | "GET": {}, 574 | "DELETE": {} 575 | }, 576 | "/wd/hub/session/:sessionId/session_storage/size": { 577 | "GET": {} 578 | }, 579 | "/wd/hub/session/:sessionId/log": { 580 | "POST": { 581 | "command": "getLog", 582 | "payloadParams": { 583 | "required": [ 584 | "type" 585 | ] 586 | } 587 | } 588 | }, 589 | "/wd/hub/session/:sessionId/log/types": { 590 | "GET": { 591 | "command": "getLogTypes" 592 | } 593 | }, 594 | "/wd/hub/session/:sessionId/application_cache/status": { 595 | "GET": {} 596 | }, 597 | "/wd/hub/session/:sessionId/context": { 598 | "GET": { 599 | "command": "getCurrentContext" 600 | }, 601 | "POST": { 602 | "command": "setContext", 603 | "payloadParams": { 604 | "required": [ 605 | "name" 606 | ] 607 | } 608 | } 609 | }, 610 | "/wd/hub/session/:sessionId/contexts": { 611 | "GET": { 612 | "command": "getContexts" 613 | } 614 | }, 615 | "/wd/hub/session/:sessionId/element/:elementId/pageIndex": { 616 | "GET": { 617 | "command": "getPageIndex" 618 | } 619 | }, 620 | "/wd/hub/session/:sessionId/network_connection": { 621 | "GET": { 622 | "command": "getNetworkConnection" 623 | }, 624 | "POST": { 625 | "command": "setNetworkConnection", 626 | "payloadParams": { 627 | "unwrap": "parameters", 628 | "required": [ 629 | "type" 630 | ] 631 | } 632 | } 633 | }, 634 | "/wd/hub/session/:sessionId/touch/perform": { 635 | "POST": { 636 | "command": "performTouch", 637 | "payloadParams": { 638 | "wrap": "actions", 639 | "required": [ 640 | "actions" 641 | ] 642 | } 643 | } 644 | }, 645 | "/wd/hub/session/:sessionId/touch/multi/perform": { 646 | "POST": { 647 | "command": "performMultiAction", 648 | "payloadParams": { 649 | "required": [ 650 | "actions" 651 | ], 652 | "optional": [ 653 | "elementId" 654 | ] 655 | } 656 | } 657 | }, 658 | "/wd/hub/session/:sessionId/receive_async_response": { 659 | "POST": { 660 | "command": "receiveAsyncResponse", 661 | "payloadParams": { 662 | "required": [ 663 | "status", 664 | "value" 665 | ] 666 | } 667 | } 668 | }, 669 | "/wd/hub/session/:sessionId/appium/device/shake": { 670 | "POST": { 671 | "command": "mobileShake" 672 | } 673 | }, 674 | "/wd/hub/session/:sessionId/appium/device/system_time": { 675 | "GET": { 676 | "command": "getDeviceTime", 677 | "payloadParams": { 678 | "optional": [ 679 | "format" 680 | ] 681 | } 682 | } 683 | }, 684 | "/wd/hub/session/:sessionId/appium/device/lock": { 685 | "POST": { 686 | "command": "lock", 687 | "payloadParams": { 688 | "optional": [ 689 | "seconds" 690 | ] 691 | } 692 | } 693 | }, 694 | "/wd/hub/session/:sessionId/appium/device/unlock": { 695 | "POST": { 696 | "command": "unlock" 697 | } 698 | }, 699 | "/wd/hub/session/:sessionId/appium/device/is_locked": { 700 | "POST": { 701 | "command": "isLocked" 702 | } 703 | }, 704 | "/wd/hub/session/:sessionId/appium/start_recording_screen": { 705 | "POST": { 706 | "command": "startRecordingScreen", 707 | "payloadParams": { 708 | "optional": [ 709 | "options" 710 | ] 711 | } 712 | } 713 | }, 714 | "/wd/hub/session/:sessionId/appium/stop_recording_screen": { 715 | "POST": { 716 | "command": "stopRecordingScreen", 717 | "payloadParams": { 718 | "optional": [ 719 | "options" 720 | ] 721 | } 722 | } 723 | }, 724 | "/wd/hub/session/:sessionId/appium/performanceData/types": { 725 | "POST": { 726 | "command": "getPerformanceDataTypes" 727 | } 728 | }, 729 | "/wd/hub/session/:sessionId/appium/getPerformanceData": { 730 | "POST": { 731 | "command": "getPerformanceData", 732 | "payloadParams": { 733 | "required": [ 734 | "packageName", 735 | "dataType" 736 | ], 737 | "optional": [ 738 | "dataReadTimeout" 739 | ] 740 | } 741 | } 742 | }, 743 | "/wd/hub/session/:sessionId/appium/device/press_keycode": { 744 | "POST": { 745 | "command": "pressKeyCode", 746 | "payloadParams": { 747 | "required": [ 748 | "keycode" 749 | ], 750 | "optional": [ 751 | "metastate", 752 | "flags" 753 | ] 754 | } 755 | } 756 | }, 757 | "/wd/hub/session/:sessionId/appium/device/long_press_keycode": { 758 | "POST": { 759 | "command": "longPressKeyCode", 760 | "payloadParams": { 761 | "required": [ 762 | "keycode" 763 | ], 764 | "optional": [ 765 | "metastate", 766 | "flags" 767 | ] 768 | } 769 | } 770 | }, 771 | "/wd/hub/session/:sessionId/appium/device/finger_print": { 772 | "POST": { 773 | "command": "fingerprint", 774 | "payloadParams": { 775 | "required": [ 776 | "fingerprintId" 777 | ] 778 | } 779 | } 780 | }, 781 | "/wd/hub/session/:sessionId/appium/device/send_sms": { 782 | "POST": { 783 | "command": "sendSMS", 784 | "payloadParams": { 785 | "required": [ 786 | "phoneNumber", 787 | "message" 788 | ] 789 | } 790 | } 791 | }, 792 | "/wd/hub/session/:sessionId/appium/device/gsm_call": { 793 | "POST": { 794 | "command": "gsmCall", 795 | "payloadParams": { 796 | "required": [ 797 | "phoneNumber", 798 | "action" 799 | ] 800 | } 801 | } 802 | }, 803 | "/wd/hub/session/:sessionId/appium/device/gsm_signal": { 804 | "POST": { 805 | "command": "gsmSignal", 806 | "payloadParams": { 807 | "required": [ 808 | "signalStrengh" 809 | ] 810 | } 811 | } 812 | }, 813 | "/wd/hub/session/:sessionId/appium/device/gsm_voice": { 814 | "POST": { 815 | "command": "gsmVoice", 816 | "payloadParams": { 817 | "required": [ 818 | "state" 819 | ] 820 | } 821 | } 822 | }, 823 | "/wd/hub/session/:sessionId/appium/device/power_capacity": { 824 | "POST": { 825 | "command": "powerCapacity", 826 | "payloadParams": { 827 | "required": [ 828 | "percent" 829 | ] 830 | } 831 | } 832 | }, 833 | "/wd/hub/session/:sessionId/appium/device/power_ac": { 834 | "POST": { 835 | "command": "powerAC", 836 | "payloadParams": { 837 | "required": [ 838 | "state" 839 | ] 840 | } 841 | } 842 | }, 843 | "/wd/hub/session/:sessionId/appium/device/network_speed": { 844 | "POST": { 845 | "command": "networkSpeed", 846 | "payloadParams": { 847 | "required": [ 848 | "netspeed" 849 | ] 850 | } 851 | } 852 | }, 853 | "/wd/hub/session/:sessionId/appium/device/keyevent": { 854 | "POST": { 855 | "command": "keyevent", 856 | "payloadParams": { 857 | "required": [ 858 | "keycode" 859 | ], 860 | "optional": [ 861 | "metastate" 862 | ] 863 | } 864 | } 865 | }, 866 | "/wd/hub/session/:sessionId/appium/device/rotate": { 867 | "POST": { 868 | "command": "mobileRotation", 869 | "payloadParams": { 870 | "required": [ 871 | "x", 872 | "y", 873 | "radius", 874 | "rotation", 875 | "touchCount", 876 | "duration" 877 | ], 878 | "optional": [ 879 | "element" 880 | ] 881 | } 882 | } 883 | }, 884 | "/wd/hub/session/:sessionId/appium/device/current_activity": { 885 | "GET": { 886 | "command": "getCurrentActivity" 887 | } 888 | }, 889 | "/wd/hub/session/:sessionId/appium/device/current_package": { 890 | "GET": { 891 | "command": "getCurrentPackage" 892 | } 893 | }, 894 | "/wd/hub/session/:sessionId/appium/device/install_app": { 895 | "POST": { 896 | "command": "installApp", 897 | "payloadParams": { 898 | "required": [ 899 | "appPath" 900 | ], 901 | "optional": [ 902 | "options" 903 | ] 904 | } 905 | } 906 | }, 907 | "/wd/hub/session/:sessionId/appium/device/activate_app": { 908 | "POST": { 909 | "command": "activateApp", 910 | "payloadParams": { 911 | "required": [ 912 | [ 913 | "appId" 914 | ], 915 | [ 916 | "bundleId" 917 | ] 918 | ], 919 | "optional": [ 920 | "options" 921 | ] 922 | } 923 | } 924 | }, 925 | "/wd/hub/session/:sessionId/appium/device/remove_app": { 926 | "POST": { 927 | "command": "removeApp", 928 | "payloadParams": { 929 | "required": [ 930 | [ 931 | "appId" 932 | ], 933 | [ 934 | "bundleId" 935 | ] 936 | ], 937 | "optional": [ 938 | "options" 939 | ] 940 | } 941 | } 942 | }, 943 | "/wd/hub/session/:sessionId/appium/device/terminate_app": { 944 | "POST": { 945 | "command": "terminateApp", 946 | "payloadParams": { 947 | "required": [ 948 | [ 949 | "appId" 950 | ], 951 | [ 952 | "bundleId" 953 | ] 954 | ], 955 | "optional": [ 956 | "options" 957 | ] 958 | } 959 | } 960 | }, 961 | "/wd/hub/session/:sessionId/appium/device/app_installed": { 962 | "POST": { 963 | "command": "isAppInstalled", 964 | "payloadParams": { 965 | "required": [ 966 | [ 967 | "appId" 968 | ], 969 | [ 970 | "bundleId" 971 | ] 972 | ] 973 | } 974 | } 975 | }, 976 | "/wd/hub/session/:sessionId/appium/device/app_state": { 977 | "GET": { 978 | "command": "queryAppState", 979 | "payloadParams": { 980 | "required": [ 981 | [ 982 | "appId" 983 | ], 984 | [ 985 | "bundleId" 986 | ] 987 | ] 988 | } 989 | }, 990 | "POST": { 991 | "command": "queryAppState", 992 | "payloadParams": { 993 | "required": [ 994 | [ 995 | "appId" 996 | ], 997 | [ 998 | "bundleId" 999 | ] 1000 | ] 1001 | } 1002 | } 1003 | }, 1004 | "/wd/hub/session/:sessionId/appium/device/hide_keyboard": { 1005 | "POST": { 1006 | "command": "hideKeyboard", 1007 | "payloadParams": { 1008 | "optional": [ 1009 | "strategy", 1010 | "key", 1011 | "keyCode", 1012 | "keyName" 1013 | ] 1014 | } 1015 | } 1016 | }, 1017 | "/wd/hub/session/:sessionId/appium/device/is_keyboard_shown": { 1018 | "GET": { 1019 | "command": "isKeyboardShown" 1020 | } 1021 | }, 1022 | "/wd/hub/session/:sessionId/appium/device/push_file": { 1023 | "POST": { 1024 | "command": "pushFile", 1025 | "payloadParams": { 1026 | "required": [ 1027 | "path", 1028 | "data" 1029 | ] 1030 | } 1031 | } 1032 | }, 1033 | "/wd/hub/session/:sessionId/appium/device/pull_file": { 1034 | "POST": { 1035 | "command": "pullFile", 1036 | "payloadParams": { 1037 | "required": [ 1038 | "path" 1039 | ] 1040 | } 1041 | } 1042 | }, 1043 | "/wd/hub/session/:sessionId/appium/device/pull_folder": { 1044 | "POST": { 1045 | "command": "pullFolder", 1046 | "payloadParams": { 1047 | "required": [ 1048 | "path" 1049 | ] 1050 | } 1051 | } 1052 | }, 1053 | "/wd/hub/session/:sessionId/appium/device/toggle_airplane_mode": { 1054 | "POST": { 1055 | "command": "toggleFlightMode" 1056 | } 1057 | }, 1058 | "/wd/hub/session/:sessionId/appium/device/toggle_data": { 1059 | "POST": { 1060 | "command": "toggleData" 1061 | } 1062 | }, 1063 | "/wd/hub/session/:sessionId/appium/device/toggle_wifi": { 1064 | "POST": { 1065 | "command": "toggleWiFi" 1066 | } 1067 | }, 1068 | "/wd/hub/session/:sessionId/appium/device/toggle_location_services": { 1069 | "POST": { 1070 | "command": "toggleLocationServices" 1071 | } 1072 | }, 1073 | "/wd/hub/session/:sessionId/appium/device/open_notifications": { 1074 | "POST": { 1075 | "command": "openNotifications" 1076 | } 1077 | }, 1078 | "/wd/hub/session/:sessionId/appium/device/start_activity": { 1079 | "POST": { 1080 | "command": "startActivity", 1081 | "payloadParams": { 1082 | "required": [ 1083 | "appPackage", 1084 | "appActivity" 1085 | ], 1086 | "optional": [ 1087 | "appWaitPackage", 1088 | "appWaitActivity", 1089 | "intentAction", 1090 | "intentCategory", 1091 | "intentFlags", 1092 | "optionalIntentArguments", 1093 | "dontStopAppOnReset" 1094 | ] 1095 | } 1096 | } 1097 | }, 1098 | "/wd/hub/session/:sessionId/appium/device/system_bars": { 1099 | "GET": { 1100 | "command": "getSystemBars" 1101 | } 1102 | }, 1103 | "/wd/hub/session/:sessionId/appium/device/display_density": { 1104 | "GET": { 1105 | "command": "getDisplayDensity" 1106 | } 1107 | }, 1108 | "/wd/hub/session/:sessionId/appium/simulator/touch_id": { 1109 | "POST": { 1110 | "command": "touchId", 1111 | "payloadParams": { 1112 | "required": [ 1113 | "match" 1114 | ] 1115 | } 1116 | } 1117 | }, 1118 | "/wd/hub/session/:sessionId/appium/simulator/toggle_touch_id_enrollment": { 1119 | "POST": { 1120 | "command": "toggleEnrollTouchId", 1121 | "payloadParams": { 1122 | "optional": [ 1123 | "enabled" 1124 | ] 1125 | } 1126 | } 1127 | }, 1128 | "/wd/hub/session/:sessionId/appium/app/launch": { 1129 | "POST": { 1130 | "command": "launchApp" 1131 | } 1132 | }, 1133 | "/wd/hub/session/:sessionId/appium/app/close": { 1134 | "POST": { 1135 | "command": "closeApp" 1136 | } 1137 | }, 1138 | "/wd/hub/session/:sessionId/appium/app/reset": { 1139 | "POST": { 1140 | "command": "reset" 1141 | } 1142 | }, 1143 | "/wd/hub/session/:sessionId/appium/app/background": { 1144 | "POST": { 1145 | "command": "background", 1146 | "payloadParams": { 1147 | "required": [ 1148 | "seconds" 1149 | ] 1150 | } 1151 | } 1152 | }, 1153 | "/wd/hub/session/:sessionId/appium/app/end_test_coverage": { 1154 | "POST": { 1155 | "command": "endCoverage", 1156 | "payloadParams": { 1157 | "required": [ 1158 | "intent", 1159 | "path" 1160 | ] 1161 | } 1162 | } 1163 | }, 1164 | "/wd/hub/session/:sessionId/appium/app/strings": { 1165 | "POST": { 1166 | "command": "getStrings", 1167 | "payloadParams": { 1168 | "optional": [ 1169 | "language", 1170 | "stringFile" 1171 | ] 1172 | } 1173 | } 1174 | }, 1175 | "/wd/hub/session/:sessionId/appium/element/:elementId/value": { 1176 | "POST": { 1177 | "command": "setValueImmediate", 1178 | "payloadParams": { 1179 | "required": [ 1180 | "value" 1181 | ] 1182 | } 1183 | } 1184 | }, 1185 | "/wd/hub/session/:sessionId/appium/element/:elementId/replace_value": { 1186 | "POST": { 1187 | "command": "replaceValue", 1188 | "payloadParams": { 1189 | "required": [ 1190 | "value" 1191 | ] 1192 | } 1193 | } 1194 | }, 1195 | "/wd/hub/session/:sessionId/appium/settings": { 1196 | "POST": { 1197 | "command": "updateSettings", 1198 | "payloadParams": { 1199 | "required": [ 1200 | "settings" 1201 | ] 1202 | } 1203 | }, 1204 | "GET": { 1205 | "command": "getSettings" 1206 | } 1207 | }, 1208 | "/wd/hub/session/:sessionId/appium/receive_async_response": { 1209 | "POST": { 1210 | "command": "receiveAsyncResponse", 1211 | "payloadParams": { 1212 | "required": [ 1213 | "response" 1214 | ] 1215 | } 1216 | } 1217 | }, 1218 | "/wd/hub/session/:sessionId/alert_text": { 1219 | "GET": { 1220 | "command": "getAlertText" 1221 | }, 1222 | "POST": { 1223 | "command": "setAlertText", 1224 | "payloadParams": { 1225 | "required": [ 1226 | "text" 1227 | ] 1228 | } 1229 | } 1230 | }, 1231 | "/wd/hub/session/:sessionId/accept_alert": { 1232 | "POST": { 1233 | "command": "postAcceptAlert" 1234 | } 1235 | }, 1236 | "/wd/hub/session/:sessionId/dismiss_alert": { 1237 | "POST": { 1238 | "command": "postDismissAlert" 1239 | } 1240 | }, 1241 | "/wd/hub/session/:sessionId/alert/text": { 1242 | "GET": { 1243 | "command": "getAlertText" 1244 | }, 1245 | "POST": { 1246 | "command": "setAlertText", 1247 | "payloadParams": { 1248 | "optional": [ 1249 | "value", 1250 | "text" 1251 | ] 1252 | } 1253 | } 1254 | }, 1255 | "/wd/hub/session/:sessionId/alert/accept": { 1256 | "POST": { 1257 | "command": "postAcceptAlert" 1258 | } 1259 | }, 1260 | "/wd/hub/session/:sessionId/alert/dismiss": { 1261 | "POST": { 1262 | "command": "postDismissAlert" 1263 | } 1264 | }, 1265 | "/wd/hub/session/:sessionId/element/:elementId/rect": { 1266 | "GET": { 1267 | "command": "getElementRect" 1268 | } 1269 | }, 1270 | "/wd/hub/session/:sessionId/execute/sync": { 1271 | "POST": { 1272 | "command": "execute", 1273 | "payloadParams": { 1274 | "required": [ 1275 | "script", 1276 | "args" 1277 | ] 1278 | } 1279 | } 1280 | }, 1281 | "/wd/hub/session/:sessionId/execute/async": { 1282 | "POST": { 1283 | "command": "executeAsync", 1284 | "payloadParams": { 1285 | "required": [ 1286 | "script", 1287 | "args" 1288 | ] 1289 | } 1290 | } 1291 | }, 1292 | "/wd/hub/session/:sessionId/screenshot/:elementId": { 1293 | "GET": { 1294 | "command": "getElementScreenshot" 1295 | } 1296 | }, 1297 | "/wd/hub/session/:sessionId/element/:elementId/screenshot": { 1298 | "GET": { 1299 | "command": "getElementScreenshot" 1300 | } 1301 | }, 1302 | "/wd/hub/session/:sessionId/window/rect": { 1303 | "GET": { 1304 | "command": "getWindowRect" 1305 | }, 1306 | "POST": { 1307 | "command": "setWindowRect" 1308 | } 1309 | }, 1310 | "/wd/hub/session/:sessionId/window/maximize": { 1311 | "POST": { 1312 | "command": "maximizeWindow" 1313 | } 1314 | }, 1315 | "/wd/hub/session/:sessionId/window/minimize": { 1316 | "POST": { 1317 | "command": "minimizeWindow" 1318 | } 1319 | }, 1320 | "/wd/hub/session/:sessionId/window/fullscreen": { 1321 | "POST": { 1322 | "command": "fullScreenWindow" 1323 | } 1324 | }, 1325 | "/wd/hub/session/:sessionId/element/:elementId/property/:name": { 1326 | "GET": { 1327 | "command": "getProperty" 1328 | } 1329 | }, 1330 | "/wd/hub/session/:sessionId/appium/device/set_clipboard": { 1331 | "POST": { 1332 | "command": "setClipboard", 1333 | "payloadParams": { 1334 | "required": [ 1335 | "content" 1336 | ], 1337 | "optional": [ 1338 | "contentType", 1339 | "label" 1340 | ] 1341 | } 1342 | } 1343 | }, 1344 | "/wd/hub/session/:sessionId/appium/device/get_clipboard": { 1345 | "POST": { 1346 | "command": "getClipboard", 1347 | "payloadParams": { 1348 | "optional": [ 1349 | "contentType" 1350 | ] 1351 | } 1352 | } 1353 | }, 1354 | "/wd/hub/session/:sessionId/appium/compare_images": { 1355 | "POST": { 1356 | "command": "compareImages", 1357 | "payloadParams": { 1358 | "required": [ 1359 | "mode", 1360 | "firstImage", 1361 | "secondImage" 1362 | ], 1363 | "optional": [ 1364 | "options" 1365 | ] 1366 | } 1367 | } 1368 | } 1369 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ajv@^6.5.5: 6 | version "6.6.2" 7 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" 8 | integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g== 9 | dependencies: 10 | fast-deep-equal "^2.0.1" 11 | fast-json-stable-stringify "^2.0.0" 12 | json-schema-traverse "^0.4.1" 13 | uri-js "^4.2.2" 14 | 15 | all-contributors-cli@^4.10.1: 16 | version "4.11.2" 17 | resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-4.11.2.tgz#b8bf1e1d08181be76ca4ebeb7869d3fdfbcf5557" 18 | integrity sha512-E1hfoxpCWes+YUvYP9IuaQMg6gs//5iRearVeDfgrxUNr6MFP0BGJwhZb33usiNCO7Sl3lasbpAfWNmfvXb1Bg== 19 | dependencies: 20 | async "^2.0.0-rc.1" 21 | chalk "^2.3.0" 22 | inquirer "^4.0.0" 23 | lodash "^4.11.2" 24 | pify "^3.0.0" 25 | request "^2.72.0" 26 | yargs "^10.0.3" 27 | 28 | ansi-escapes@^3.0.0: 29 | version "3.1.0" 30 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" 31 | integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== 32 | 33 | ansi-regex@^2.0.0: 34 | version "2.1.1" 35 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 36 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 37 | 38 | ansi-regex@^3.0.0: 39 | version "3.0.0" 40 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 41 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 42 | 43 | ansi-styles@^3.2.1: 44 | version "3.2.1" 45 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 46 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 47 | dependencies: 48 | color-convert "^1.9.0" 49 | 50 | asn1@~0.2.3: 51 | version "0.2.4" 52 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 53 | integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== 54 | dependencies: 55 | safer-buffer "~2.1.0" 56 | 57 | assert-plus@1.0.0, assert-plus@^1.0.0: 58 | version "1.0.0" 59 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 60 | integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= 61 | 62 | async@^2.0.0-rc.1: 63 | version "2.6.1" 64 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" 65 | integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== 66 | dependencies: 67 | lodash "^4.17.10" 68 | 69 | asynckit@^0.4.0: 70 | version "0.4.0" 71 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 72 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 73 | 74 | aws-sign2@~0.7.0: 75 | version "0.7.0" 76 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 77 | integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= 78 | 79 | aws4@^1.8.0: 80 | version "1.8.0" 81 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" 82 | integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== 83 | 84 | bcrypt-pbkdf@^1.0.0: 85 | version "1.0.2" 86 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 87 | integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= 88 | dependencies: 89 | tweetnacl "^0.14.3" 90 | 91 | camelcase@^4.1.0: 92 | version "4.1.0" 93 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" 94 | integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= 95 | 96 | caseless@~0.12.0: 97 | version "0.12.0" 98 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 99 | integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= 100 | 101 | chalk@^2.0.0, chalk@^2.3.0: 102 | version "2.4.1" 103 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" 104 | integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== 105 | dependencies: 106 | ansi-styles "^3.2.1" 107 | escape-string-regexp "^1.0.5" 108 | supports-color "^5.3.0" 109 | 110 | chardet@^0.4.0: 111 | version "0.4.2" 112 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" 113 | integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= 114 | 115 | cli-cursor@^2.1.0: 116 | version "2.1.0" 117 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 118 | integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= 119 | dependencies: 120 | restore-cursor "^2.0.0" 121 | 122 | cli-width@^2.0.0: 123 | version "2.2.0" 124 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 125 | integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= 126 | 127 | cliui@^4.0.0: 128 | version "4.1.0" 129 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" 130 | integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== 131 | dependencies: 132 | string-width "^2.1.1" 133 | strip-ansi "^4.0.0" 134 | wrap-ansi "^2.0.0" 135 | 136 | code-point-at@^1.0.0: 137 | version "1.1.0" 138 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 139 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 140 | 141 | color-convert@^1.9.0: 142 | version "1.9.3" 143 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 144 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 145 | dependencies: 146 | color-name "1.1.3" 147 | 148 | color-name@1.1.3: 149 | version "1.1.3" 150 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 151 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 152 | 153 | combined-stream@^1.0.6, combined-stream@~1.0.6: 154 | version "1.0.7" 155 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" 156 | integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== 157 | dependencies: 158 | delayed-stream "~1.0.0" 159 | 160 | core-util-is@1.0.2: 161 | version "1.0.2" 162 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 163 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 164 | 165 | cross-spawn@^5.0.1: 166 | version "5.1.0" 167 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 168 | integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= 169 | dependencies: 170 | lru-cache "^4.0.1" 171 | shebang-command "^1.2.0" 172 | which "^1.2.9" 173 | 174 | dashdash@^1.12.0: 175 | version "1.14.1" 176 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 177 | integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= 178 | dependencies: 179 | assert-plus "^1.0.0" 180 | 181 | decamelize@^1.1.1: 182 | version "1.2.0" 183 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 184 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 185 | 186 | delayed-stream@~1.0.0: 187 | version "1.0.0" 188 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 189 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 190 | 191 | ecc-jsbn@~0.1.1: 192 | version "0.1.2" 193 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 194 | integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= 195 | dependencies: 196 | jsbn "~0.1.0" 197 | safer-buffer "^2.1.0" 198 | 199 | escape-string-regexp@^1.0.5: 200 | version "1.0.5" 201 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 202 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 203 | 204 | execa@^0.7.0: 205 | version "0.7.0" 206 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" 207 | integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= 208 | dependencies: 209 | cross-spawn "^5.0.1" 210 | get-stream "^3.0.0" 211 | is-stream "^1.1.0" 212 | npm-run-path "^2.0.0" 213 | p-finally "^1.0.0" 214 | signal-exit "^3.0.0" 215 | strip-eof "^1.0.0" 216 | 217 | extend@~3.0.2: 218 | version "3.0.2" 219 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 220 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 221 | 222 | external-editor@^2.1.0: 223 | version "2.2.0" 224 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" 225 | integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== 226 | dependencies: 227 | chardet "^0.4.0" 228 | iconv-lite "^0.4.17" 229 | tmp "^0.0.33" 230 | 231 | extsprintf@1.3.0: 232 | version "1.3.0" 233 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 234 | integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= 235 | 236 | extsprintf@^1.2.0: 237 | version "1.4.0" 238 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" 239 | integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= 240 | 241 | fast-deep-equal@^2.0.1: 242 | version "2.0.1" 243 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" 244 | integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= 245 | 246 | fast-json-stable-stringify@^2.0.0: 247 | version "2.0.0" 248 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 249 | integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= 250 | 251 | figures@^2.0.0: 252 | version "2.0.0" 253 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 254 | integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= 255 | dependencies: 256 | escape-string-regexp "^1.0.5" 257 | 258 | find-up@^2.1.0: 259 | version "2.1.0" 260 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" 261 | integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= 262 | dependencies: 263 | locate-path "^2.0.0" 264 | 265 | forever-agent@~0.6.1: 266 | version "0.6.1" 267 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 268 | integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= 269 | 270 | form-data@~2.3.2: 271 | version "2.3.3" 272 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" 273 | integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== 274 | dependencies: 275 | asynckit "^0.4.0" 276 | combined-stream "^1.0.6" 277 | mime-types "^2.1.12" 278 | 279 | get-caller-file@^1.0.1: 280 | version "1.0.3" 281 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" 282 | integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== 283 | 284 | get-stream@^3.0.0: 285 | version "3.0.0" 286 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 287 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= 288 | 289 | getpass@^0.1.1: 290 | version "0.1.7" 291 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 292 | integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= 293 | dependencies: 294 | assert-plus "^1.0.0" 295 | 296 | har-schema@^2.0.0: 297 | version "2.0.0" 298 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" 299 | integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= 300 | 301 | har-validator@~5.1.0: 302 | version "5.1.3" 303 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" 304 | integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== 305 | dependencies: 306 | ajv "^6.5.5" 307 | har-schema "^2.0.0" 308 | 309 | has-flag@^3.0.0: 310 | version "3.0.0" 311 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 312 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 313 | 314 | http-signature@~1.2.0: 315 | version "1.2.0" 316 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 317 | integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= 318 | dependencies: 319 | assert-plus "^1.0.0" 320 | jsprim "^1.2.2" 321 | sshpk "^1.7.0" 322 | 323 | iconv-lite@^0.4.17: 324 | version "0.4.24" 325 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 326 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 327 | dependencies: 328 | safer-buffer ">= 2.1.2 < 3" 329 | 330 | inquirer@^4.0.0: 331 | version "4.0.2" 332 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-4.0.2.tgz#cc678b4cbc0e183a3500cc63395831ec956ab0a3" 333 | integrity sha512-+f3qDNeZpkhFJ61NBA9jXDrGGhoQuqfEum9A681c9oHoIbGgVqjogKynjB/vNVP+nVu9w3FbFQ35c0ibU0MaIQ== 334 | dependencies: 335 | ansi-escapes "^3.0.0" 336 | chalk "^2.0.0" 337 | cli-cursor "^2.1.0" 338 | cli-width "^2.0.0" 339 | external-editor "^2.1.0" 340 | figures "^2.0.0" 341 | lodash "^4.3.0" 342 | mute-stream "0.0.7" 343 | run-async "^2.2.0" 344 | rx-lite "^4.0.8" 345 | rx-lite-aggregates "^4.0.8" 346 | string-width "^2.1.0" 347 | strip-ansi "^4.0.0" 348 | through "^2.3.6" 349 | 350 | invert-kv@^1.0.0: 351 | version "1.0.0" 352 | resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" 353 | integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= 354 | 355 | is-fullwidth-code-point@^1.0.0: 356 | version "1.0.0" 357 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 358 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 359 | dependencies: 360 | number-is-nan "^1.0.0" 361 | 362 | is-fullwidth-code-point@^2.0.0: 363 | version "2.0.0" 364 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 365 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 366 | 367 | is-promise@^2.1.0: 368 | version "2.1.0" 369 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 370 | integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= 371 | 372 | is-stream@^1.1.0: 373 | version "1.1.0" 374 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 375 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 376 | 377 | is-typedarray@~1.0.0: 378 | version "1.0.0" 379 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 380 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 381 | 382 | isexe@^2.0.0: 383 | version "2.0.0" 384 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 385 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 386 | 387 | isstream@~0.1.2: 388 | version "0.1.2" 389 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 390 | integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= 391 | 392 | jsbn@~0.1.0: 393 | version "0.1.1" 394 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 395 | integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= 396 | 397 | json-schema-traverse@^0.4.1: 398 | version "0.4.1" 399 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 400 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 401 | 402 | json-schema@0.2.3: 403 | version "0.2.3" 404 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 405 | integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= 406 | 407 | json-stringify-safe@~5.0.1: 408 | version "5.0.1" 409 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 410 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 411 | 412 | jsprim@^1.2.2: 413 | version "1.4.1" 414 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" 415 | integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= 416 | dependencies: 417 | assert-plus "1.0.0" 418 | extsprintf "1.3.0" 419 | json-schema "0.2.3" 420 | verror "1.10.0" 421 | 422 | lcid@^1.0.0: 423 | version "1.0.0" 424 | resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" 425 | integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= 426 | dependencies: 427 | invert-kv "^1.0.0" 428 | 429 | locate-path@^2.0.0: 430 | version "2.0.0" 431 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" 432 | integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= 433 | dependencies: 434 | p-locate "^2.0.0" 435 | path-exists "^3.0.0" 436 | 437 | lodash@^4.11.2, lodash@^4.17.10, lodash@^4.3.0: 438 | version "4.17.11" 439 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" 440 | integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== 441 | 442 | lru-cache@^4.0.1: 443 | version "4.1.5" 444 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" 445 | integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== 446 | dependencies: 447 | pseudomap "^1.0.2" 448 | yallist "^2.1.2" 449 | 450 | mem@^1.1.0: 451 | version "1.1.0" 452 | resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" 453 | integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= 454 | dependencies: 455 | mimic-fn "^1.0.0" 456 | 457 | mime-db@~1.37.0: 458 | version "1.37.0" 459 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" 460 | integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== 461 | 462 | mime-types@^2.1.12, mime-types@~2.1.19: 463 | version "2.1.21" 464 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" 465 | integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== 466 | dependencies: 467 | mime-db "~1.37.0" 468 | 469 | mimic-fn@^1.0.0: 470 | version "1.2.0" 471 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" 472 | integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== 473 | 474 | mute-stream@0.0.7: 475 | version "0.0.7" 476 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 477 | integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= 478 | 479 | npm-run-path@^2.0.0: 480 | version "2.0.2" 481 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 482 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= 483 | dependencies: 484 | path-key "^2.0.0" 485 | 486 | number-is-nan@^1.0.0: 487 | version "1.0.1" 488 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 489 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 490 | 491 | oauth-sign@~0.9.0: 492 | version "0.9.0" 493 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 494 | integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== 495 | 496 | onetime@^2.0.0: 497 | version "2.0.1" 498 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 499 | integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= 500 | dependencies: 501 | mimic-fn "^1.0.0" 502 | 503 | os-locale@^2.0.0: 504 | version "2.1.0" 505 | resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" 506 | integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== 507 | dependencies: 508 | execa "^0.7.0" 509 | lcid "^1.0.0" 510 | mem "^1.1.0" 511 | 512 | os-tmpdir@~1.0.2: 513 | version "1.0.2" 514 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 515 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 516 | 517 | p-finally@^1.0.0: 518 | version "1.0.0" 519 | resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 520 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 521 | 522 | p-limit@^1.1.0: 523 | version "1.3.0" 524 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" 525 | integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== 526 | dependencies: 527 | p-try "^1.0.0" 528 | 529 | p-locate@^2.0.0: 530 | version "2.0.0" 531 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" 532 | integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= 533 | dependencies: 534 | p-limit "^1.1.0" 535 | 536 | p-try@^1.0.0: 537 | version "1.0.0" 538 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" 539 | integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= 540 | 541 | path-exists@^3.0.0: 542 | version "3.0.0" 543 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 544 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 545 | 546 | path-key@^2.0.0: 547 | version "2.0.1" 548 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 549 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 550 | 551 | performance-now@^2.1.0: 552 | version "2.1.0" 553 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 554 | integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= 555 | 556 | pify@^3.0.0: 557 | version "3.0.0" 558 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 559 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 560 | 561 | pseudomap@^1.0.2: 562 | version "1.0.2" 563 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 564 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 565 | 566 | psl@^1.1.24: 567 | version "1.1.31" 568 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" 569 | integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== 570 | 571 | punycode@^1.4.1: 572 | version "1.4.1" 573 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 574 | integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= 575 | 576 | punycode@^2.1.0: 577 | version "2.1.1" 578 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 579 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 580 | 581 | qs@~6.5.2: 582 | version "6.5.3" 583 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" 584 | integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== 585 | 586 | request@^2.72.0: 587 | version "2.88.0" 588 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" 589 | integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== 590 | dependencies: 591 | aws-sign2 "~0.7.0" 592 | aws4 "^1.8.0" 593 | caseless "~0.12.0" 594 | combined-stream "~1.0.6" 595 | extend "~3.0.2" 596 | forever-agent "~0.6.1" 597 | form-data "~2.3.2" 598 | har-validator "~5.1.0" 599 | http-signature "~1.2.0" 600 | is-typedarray "~1.0.0" 601 | isstream "~0.1.2" 602 | json-stringify-safe "~5.0.1" 603 | mime-types "~2.1.19" 604 | oauth-sign "~0.9.0" 605 | performance-now "^2.1.0" 606 | qs "~6.5.2" 607 | safe-buffer "^5.1.2" 608 | tough-cookie "~2.4.3" 609 | tunnel-agent "^0.6.0" 610 | uuid "^3.3.2" 611 | 612 | require-directory@^2.1.1: 613 | version "2.1.1" 614 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 615 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 616 | 617 | require-main-filename@^1.0.1: 618 | version "1.0.1" 619 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 620 | integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= 621 | 622 | restore-cursor@^2.0.0: 623 | version "2.0.0" 624 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 625 | integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= 626 | dependencies: 627 | onetime "^2.0.0" 628 | signal-exit "^3.0.2" 629 | 630 | run-async@^2.2.0: 631 | version "2.3.0" 632 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 633 | integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= 634 | dependencies: 635 | is-promise "^2.1.0" 636 | 637 | rx-lite-aggregates@^4.0.8: 638 | version "4.0.8" 639 | resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" 640 | integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= 641 | dependencies: 642 | rx-lite "*" 643 | 644 | rx-lite@*, rx-lite@^4.0.8: 645 | version "4.0.8" 646 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" 647 | integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= 648 | 649 | safe-buffer@^5.0.1, safe-buffer@^5.1.2: 650 | version "5.1.2" 651 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 652 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 653 | 654 | "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 655 | version "2.1.2" 656 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 657 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 658 | 659 | set-blocking@^2.0.0: 660 | version "2.0.0" 661 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 662 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 663 | 664 | shebang-command@^1.2.0: 665 | version "1.2.0" 666 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 667 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 668 | dependencies: 669 | shebang-regex "^1.0.0" 670 | 671 | shebang-regex@^1.0.0: 672 | version "1.0.0" 673 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 674 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 675 | 676 | signal-exit@^3.0.0, signal-exit@^3.0.2: 677 | version "3.0.2" 678 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 679 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 680 | 681 | sshpk@^1.7.0: 682 | version "1.16.0" 683 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" 684 | integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ== 685 | dependencies: 686 | asn1 "~0.2.3" 687 | assert-plus "^1.0.0" 688 | bcrypt-pbkdf "^1.0.0" 689 | dashdash "^1.12.0" 690 | ecc-jsbn "~0.1.1" 691 | getpass "^0.1.1" 692 | jsbn "~0.1.0" 693 | safer-buffer "^2.0.2" 694 | tweetnacl "~0.14.0" 695 | 696 | string-width@^1.0.1: 697 | version "1.0.2" 698 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 699 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 700 | dependencies: 701 | code-point-at "^1.0.0" 702 | is-fullwidth-code-point "^1.0.0" 703 | strip-ansi "^3.0.0" 704 | 705 | string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: 706 | version "2.1.1" 707 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 708 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 709 | dependencies: 710 | is-fullwidth-code-point "^2.0.0" 711 | strip-ansi "^4.0.0" 712 | 713 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 714 | version "3.0.1" 715 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 716 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 717 | dependencies: 718 | ansi-regex "^2.0.0" 719 | 720 | strip-ansi@^4.0.0: 721 | version "4.0.0" 722 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 723 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 724 | dependencies: 725 | ansi-regex "^3.0.0" 726 | 727 | strip-eof@^1.0.0: 728 | version "1.0.0" 729 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 730 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= 731 | 732 | supports-color@^5.3.0: 733 | version "5.5.0" 734 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 735 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 736 | dependencies: 737 | has-flag "^3.0.0" 738 | 739 | through@^2.3.6: 740 | version "2.3.8" 741 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 742 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 743 | 744 | tmp@^0.0.33: 745 | version "0.0.33" 746 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 747 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 748 | dependencies: 749 | os-tmpdir "~1.0.2" 750 | 751 | tough-cookie@~2.4.3: 752 | version "2.4.3" 753 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" 754 | integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== 755 | dependencies: 756 | psl "^1.1.24" 757 | punycode "^1.4.1" 758 | 759 | tunnel-agent@^0.6.0: 760 | version "0.6.0" 761 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 762 | integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= 763 | dependencies: 764 | safe-buffer "^5.0.1" 765 | 766 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 767 | version "0.14.5" 768 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 769 | integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= 770 | 771 | uri-js@^4.2.2: 772 | version "4.2.2" 773 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 774 | integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== 775 | dependencies: 776 | punycode "^2.1.0" 777 | 778 | uuid@^3.3.2: 779 | version "3.3.2" 780 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" 781 | integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== 782 | 783 | verror@1.10.0: 784 | version "1.10.0" 785 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 786 | integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= 787 | dependencies: 788 | assert-plus "^1.0.0" 789 | core-util-is "1.0.2" 790 | extsprintf "^1.2.0" 791 | 792 | which-module@^2.0.0: 793 | version "2.0.0" 794 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 795 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 796 | 797 | which@^1.2.9: 798 | version "1.3.1" 799 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 800 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 801 | dependencies: 802 | isexe "^2.0.0" 803 | 804 | wrap-ansi@^2.0.0: 805 | version "2.1.0" 806 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" 807 | integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= 808 | dependencies: 809 | string-width "^1.0.1" 810 | strip-ansi "^3.0.1" 811 | 812 | y18n@^3.2.1: 813 | version "3.2.1" 814 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" 815 | integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= 816 | 817 | yallist@^2.1.2: 818 | version "2.1.2" 819 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 820 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 821 | 822 | yargs-parser@^8.1.0: 823 | version "8.1.0" 824 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" 825 | integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ== 826 | dependencies: 827 | camelcase "^4.1.0" 828 | 829 | yargs@^10.0.3: 830 | version "10.1.2" 831 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" 832 | integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig== 833 | dependencies: 834 | cliui "^4.0.0" 835 | decamelize "^1.1.1" 836 | find-up "^2.1.0" 837 | get-caller-file "^1.0.1" 838 | os-locale "^2.0.0" 839 | require-directory "^2.1.1" 840 | require-main-filename "^1.0.1" 841 | set-blocking "^2.0.0" 842 | string-width "^2.0.0" 843 | which-module "^2.0.0" 844 | y18n "^3.2.1" 845 | yargs-parser "^8.1.0" 846 | --------------------------------------------------------------------------------