├── .gitignore ├── web ├── .DS_Store ├── img │ ├── .DS_Store │ ├── exit.png │ ├── home.png │ ├── iphone.png │ ├── iphone5s.png │ ├── iphone6s.png │ ├── iphonex.png │ ├── nature_1.jpg │ ├── nature_2.jpg │ ├── nature_3.jpg │ ├── nature_4.jpg │ ├── nature_5.jpg │ ├── nature_6.jpg │ ├── nature_7.jpg │ ├── nature_8.jpg │ ├── nature_9.jpg │ ├── rotate.png │ ├── nature_10.jpg │ ├── iphone5s.svg.png │ ├── iphone6s.svg.png │ ├── iphonese.svg.png │ ├── iphonex.svg.png │ ├── iphonexsmax.png │ ├── iphonexsmax.svg.png │ ├── takescreenshot.svg │ ├── sandclock.svg │ └── home.svg ├── js │ ├── .DS_Store │ ├── package.json │ ├── test__appium.py │ ├── ios.js │ ├── reconnecting-websocket.js │ ├── server.js │ └── jquery.min.js ├── draw.html ├── start.html └── index.html ├── CHANGELOG.md ├── starttests.sh ├── LICENSE ├── smile.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock* 2 | *.log 3 | node_modules -------------------------------------------------------------------------------- /web/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/.DS_Store -------------------------------------------------------------------------------- /web/img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/.DS_Store -------------------------------------------------------------------------------- /web/img/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/exit.png -------------------------------------------------------------------------------- /web/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/home.png -------------------------------------------------------------------------------- /web/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/js/.DS_Store -------------------------------------------------------------------------------- /web/img/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphone.png -------------------------------------------------------------------------------- /web/img/iphone5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphone5s.png -------------------------------------------------------------------------------- /web/img/iphone6s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphone6s.png -------------------------------------------------------------------------------- /web/img/iphonex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphonex.png -------------------------------------------------------------------------------- /web/img/nature_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_1.jpg -------------------------------------------------------------------------------- /web/img/nature_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_2.jpg -------------------------------------------------------------------------------- /web/img/nature_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_3.jpg -------------------------------------------------------------------------------- /web/img/nature_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_4.jpg -------------------------------------------------------------------------------- /web/img/nature_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_5.jpg -------------------------------------------------------------------------------- /web/img/nature_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_6.jpg -------------------------------------------------------------------------------- /web/img/nature_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_7.jpg -------------------------------------------------------------------------------- /web/img/nature_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_8.jpg -------------------------------------------------------------------------------- /web/img/nature_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_9.jpg -------------------------------------------------------------------------------- /web/img/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/rotate.png -------------------------------------------------------------------------------- /web/img/nature_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/nature_10.jpg -------------------------------------------------------------------------------- /web/img/iphone5s.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphone5s.svg.png -------------------------------------------------------------------------------- /web/img/iphone6s.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphone6s.svg.png -------------------------------------------------------------------------------- /web/img/iphonese.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphonese.svg.png -------------------------------------------------------------------------------- /web/img/iphonex.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphonex.svg.png -------------------------------------------------------------------------------- /web/img/iphonexsmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphonexsmax.png -------------------------------------------------------------------------------- /web/img/iphonexsmax.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kavichki-test/ios-farm/HEAD/web/img/iphonexsmax.svg.png -------------------------------------------------------------------------------- /web/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "node-ios-device": "^1.9.2", 4 | "nodemon": "^2.0.7", 5 | "websocket": "^1.0.34", 6 | "websockets": "^0.2.0", 7 | "ws": "^7.4.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | 9 | ## [0.0.1] - 2021-05-07 10 | 11 | ### Added 12 | - Pre-alpha release 13 | -------------------------------------------------------------------------------- /starttests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LOCAL__PATH="$(pwd)" 4 | TEST_WITH=python3 5 | TEST_NAME=$LOCAL__PATH/test__appium.py 6 | UDID=$1 7 | DEVICE_NAME=$2 8 | 9 | udid=$UDID devicename=$DEVICE_NAME $TEST_WITH $TEST_NAME >> $LOCAL__PATH/log/python_test.log & 10 | 11 | # udid=42bc4690b24da66f0a3640b6a39127f29e8cddaa devicename='iPhone SE' $TEST_WITH $TEST_NAME >> $LOCAL__PATH/log/python_test.log & -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE": 4 | * Kavichki wrote this code. As long as you retain this notice you 5 | * can do whatever you want with this stuff. If we meet some day, and you think 6 | * this stuff is worth it, you can buy us a beer in return. 7 | * ---------------------------------------------------------------------------- 8 | */ -------------------------------------------------------------------------------- /smile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | WEBDRIVER__PATH="/usr/local/lib/node_modules/appium/node_modules/appium-webdriveragent" # classic path to module, which you build in XCode 4 | UDID=$1 5 | 6 | xcodebuild -project $WEBDRIVER__PATH/WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "id=$UDID" test | while read line ; do 7 | if echo "$line" | grep "ServerURLHere->*<-ServerURLHer" 8 | then 9 | echo {\"data\": \"run\"} 10 | else 11 | echo {\"data\": \"$line\"} 12 | fi 13 | done -------------------------------------------------------------------------------- /web/img/takescreenshot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Requirements (the latest stable version): 2 | - Appium 3 | - Node (with packages 'ws' and 'node-ios-device') 4 | - python 3 (with packages 'wedsocket', 'websocket-cliet', 'asyncio') 5 | - XCode 6 | 7 | ## Preconditions: 8 | - Replace "path" in "web/js/server.js" (line 119) with folder path 9 | - Replace "xcodeOrgId" in "test__appium.py" (line 40) with your Apple Developer Id 10 | - Replace "WEBDRIVER__PATH" in "smile.sh" (line 3) with "appium-webdriver" node module path 11 | - You must add a new device by id in function "run()" (web/start.html (line 125)) for catching action by click on your device on start page 12 | 13 | ## Steps: 14 | - Open XCode and build WebdriverAgentRunner for your devices 15 | - Allow certificate on your devices 16 | - Start server "node server.js" (from web/js) 17 | - Run "start.html" (from web) in browser (Google Chrome recommended) 18 | - Probabbly, your first start will be failed due to unapproved certificate. You should restart "node server.js" 19 | 20 | Running sreencasting may take about 2-3 minutes (for first launching) 21 | -------------------------------------------------------------------------------- /web/img/sandclock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /web/img/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /web/js/test__appium.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | import string 7 | import random 8 | import time 9 | import sys 10 | import asyncio 11 | import websockets 12 | import json 13 | import random 14 | import datetime 15 | 16 | from appium import webdriver 17 | from appium.webdriver.common.touch_action import TouchAction 18 | from websocket import create_connection 19 | 20 | class BasicTest(unittest.TestCase): 21 | udid = "" 22 | devicename = "" 23 | 24 | def setUp(self): 25 | now = datetime.datetime.now() 26 | print (now.strftime("%Y-%m-%d %H:%M") + ": Test preparation"); 27 | bundle_id = "com.apple.AppStore" 28 | device_name = str(self.devicename) 29 | udid = str(self.udid) 30 | self.driver = webdriver.Remote( 31 | command_executor='http://127.0.0.1:4723/wd/hub', 32 | desired_capabilities={ 33 | 'bundleId': bundle_id, 34 | 'platformName': 'iOS', 35 | 'platformVersion': "14.4", 36 | 'deviceName': device_name, 37 | 'udid': udid, 38 | "realDevice": True, 39 | 'realDeviceLogger': 'idevicesyslog', 40 | "xcodeOrgId": "ENTER YOUR ID", 41 | "xcodeSigningId": "iPhone Developer" 42 | }) 43 | self.driver.background_app(-1) 44 | 45 | def test_ui_clearing(self): 46 | now = datetime.datetime.now() 47 | udid = self.udid 48 | HOST = 'ws://localhost' # The server's hostname or IP address 49 | PORT = ':9030/:' # The port used by the server 50 | 51 | uri = HOST + PORT + udid + '/commuter' 52 | 53 | ws = create_connection(uri) 54 | print (now.strftime("%Y-%m-%d %H:%M") + ": We are ready to start"); 55 | ws.send(json.dumps({'action': 'deviceready', 'udid': udid, 'whoyouare': 'commuter'})); 56 | while True: 57 | ws.send(json.dumps({"action": "givemeaction", "toudid": udid, 'whoyouare': 'commuter'})) 58 | response = json.loads(ws.recv()) 59 | 60 | if response["action"] == 'tap': 61 | TouchAction(self.driver).tap(None, response["x"], response["y"]).release().perform() 62 | print (now.strftime("%Y-%m-%d %H:%M") + ": Make tap") 63 | elif response["action"] == 'home': 64 | self.driver.background_app(-1) 65 | print (now.strftime("%Y-%m-%d %H:%M") + ": Make home") 66 | elif response["action"] == 'swipe': 67 | self.driver.swipe(response["x0"], response["y0"], response["x1"], response["y1"], 500); 68 | print (now.strftime("%Y-%m-%d %H:%M") + ": Make swipe") 69 | elif response["action"] == 'doubletap': 70 | self.driver.double_tap(None, response["x"], response["y"]).release().perform() 71 | print (now.strftime("%Y-%m-%d %H:%M") + ": Make double tap") 72 | elif response["action"] == 'longtap': 73 | self.driver.long_press(None, response["x"], response["y"]).release().perform() 74 | print (now.strftime("%Y-%m-%d %H:%M") + ": Make long tap") 75 | elif response["action"] == 'landscape': 76 | try: 77 | self.driver.orientation = "LANDSCAPE" 78 | print (now.strftime("%Y-%m-%d %H:%M") + ": Change orientation to LANDSCAPE") 79 | except: 80 | print (now.strftime("%Y-%m-%d %H:%M") + ": Couldn't change orientation to LANDSCAPE") 81 | elif response["action"] == 'portrait': 82 | try: 83 | self.driver.orientation = "PORTRAIT" 84 | print (now.strftime("%Y-%m-%d %H:%M") + ": Change orientation to PORTRAIT") 85 | except: 86 | print (now.strftime("%Y-%m-%d %H:%M") + ": Couldn't change orientation to PORTRAIT") 87 | 88 | def tearDown(self): 89 | self.driver.quit() 90 | 91 | if __name__ == '__main__': 92 | BasicTest.udid = os.environ.get('udid', BasicTest.udid) 93 | BasicTest.devicename = os.environ.get('devicename', BasicTest.devicename) 94 | suite = unittest.TestLoader().loadTestsFromTestCase(BasicTest) 95 | unittest.TextTestRunner(verbosity=2).run(suite) -------------------------------------------------------------------------------- /web/js/ios.js: -------------------------------------------------------------------------------- 1 | var socket = null; 2 | var x0, x1, y0, y1 = 0; 3 | var device__position = "portrait"; 4 | 5 | function Rotate() { 6 | let angle = '90'; 7 | 8 | switch (device__position){ 9 | case 'portrait': 10 | MakeLandscape(); 11 | $('#img').height('600px'); 12 | break; 13 | case 'landscape': 14 | MakePortrait(); 15 | break; 16 | } 17 | } 18 | 19 | function Swipe() { 20 | socket.send(JSON.stringify({"whoyouare": "device", "toudid": $("img#img").attr('data-udid'), "action": "swipe", "x0": x0, "x1": x1, "y0": y0, "y1": y1})); 21 | } 22 | 23 | function StartSwipe(event) { 24 | x0 = Math.round(((event.pageX - $('img#img').offset().left)*93)/100); 25 | y0 = Math.round(((event.pageY - $('img#img').offset().top)*93)/100); 26 | } 27 | 28 | function StopSwipe(event) { 29 | x1 = Math.round(((event.pageX - $('img#img').offset().left)*93)/100); 30 | y1 = Math.round(((event.pageY - $('img#img').offset().top)*93)/100); 31 | 32 | if ((x0 > x1 + 50)|| 33 | (y0 > y1 + 50)|| 34 | (x1 > x0 + 50)|| 35 | (y1 > y0 + 50) 36 | ) { 37 | Swipe(); 38 | } 39 | } 40 | 41 | function MakePortrait() { 42 | socket.send(JSON.stringify({"whoyouare": "device", "toudid": $("img#img").attr('data-udid'), "action": "portrait"})); 43 | } 44 | 45 | function MakeLandscape() { 46 | socket.send(JSON.stringify({"whoyouare": "device", "toudid": $("img#img").attr('data-udid'), "action": "landscape"})); 47 | } 48 | 49 | function GoHome() { 50 | socket.send(JSON.stringify({"whoyouare": "device", "toudid": $("img#img").attr('data-udid'), "action": "home"})); 51 | } 52 | 53 | function Point(event) { 54 | let positionX = Math.round(((event.pageX - $('img#img').offset().left)*93)/100); 55 | let positionY = Math.round(((event.pageY - $('img#img').offset().top)*93)/100); 56 | 57 | socket.send(JSON.stringify({"whoyouare": "device", "toudid": $("img#img").attr('data-udid'), "action": "tap", x: positionX, y: positionY})); 58 | } 59 | 60 | function getRandomInt(min, max) { 61 | return Math.floor(Math.random() * (max - min)) + min; 62 | } 63 | 64 | function WebSocketTest() { 65 | if ("WebSocket" in window) { 66 | id = getRandomInt(100, 999); 67 | var img = document.getElementById("img"); 68 | var udid = window.location.search.substr(1); 69 | socket = new WebSocket("ws://localhost:9030/:" + id + "/device"); 70 | var ws = new ReconnectingWebSocket('ws://' + location.host + '/'); 71 | 72 | $('#img').attr('data-udid', udid); 73 | document.getElementById("img").addEventListener("click" , Point); 74 | document.getElementById("img").addEventListener("mousedown" , StartSwipe); 75 | document.getElementById("img").addEventListener("mouseup" , StopSwipe); 76 | 77 | socket.onopen = function() { 78 | socket.send(JSON.stringify({"whoyouare": "device", "action": 'deviceconnected', 'udid': window.location.search.substr(1)})); 79 | console.log('Client connected to Server'); 80 | } 81 | 82 | socket.onmessage = function (event) { 83 | response = JSON.parse(event.data); 84 | console.log(event); 85 | $('.loader').hide(); 86 | $('.outer').show(); 87 | } 88 | 89 | ws.onopen = function() { 90 | console.log("Socket is open"); 91 | }; 92 | 93 | ws.onmessage = function (evt) { 94 | response = JSON.parse(evt.data); 95 | if (response["data"].indexOf("ServerURLHere->") !== -1) { 96 | let broadcasting = response["data"].substring(response["data"].indexOf("ServerURLHere->") + "ServerURLHere->".length, response["data"].length); 97 | broadcasting = broadcasting.substring(0, broadcasting.indexOf("<-ServerURLHere")); 98 | console.log(broadcasting); 99 | img.src = broadcasting.replace("8100", "9100"); 100 | socket.send(JSON.stringify({"whoyouare": "device", "action": 'starttests', 'udid': window.location.search.substr(1)})); 101 | } 102 | }; 103 | 104 | ws.onclose = function() { 105 | }; 106 | } else { 107 | alert("WebSocket NOT supported by your Browser!"); 108 | } 109 | } 110 | 111 | -------------------------------------------------------------------------------- /web/draw.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /web/start.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58 | 59 | 60 | 146 | 147 | 148 | 149 |
150 |
151 |
152 |
153 |
iPhone XS Max
154 |
155 |
156 |
157 |
iPhone 11 Pro Max
158 |
159 |
160 |
161 |
iPhone X
162 |
163 |
164 |
165 |
iPhone 6S
166 |
167 |
168 |
169 |
iPhone 5S
170 |
171 |
172 |
173 | 174 | 181 | oNline Web Fonts 182 | 183 | -------------------------------------------------------------------------------- /web/js/reconnecting-websocket.js: -------------------------------------------------------------------------------- 1 | // MIT License: 2 | // 3 | // Copyright (c) 2010-2012, Joe Walnes 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | /** 24 | * This behaves like a WebSocket in every way, except if it fails to connect, 25 | * or it gets disconnected, it will repeatedly poll until it succesfully connects 26 | * again. 27 | * 28 | * It is API compatible, so when you have: 29 | * ws = new WebSocket('ws://....'); 30 | * you can replace with: 31 | * ws = new ReconnectingWebSocket('ws://....'); 32 | * 33 | * The event stream will typically look like: 34 | * onconnecting 35 | * onopen 36 | * onmessage 37 | * onmessage 38 | * onclose // lost connection 39 | * onconnecting 40 | * onopen // sometime later... 41 | * onmessage 42 | * onmessage 43 | * etc... 44 | * 45 | * It is API compatible with the standard WebSocket API. 46 | * 47 | * Latest version: https://github.com/joewalnes/reconnecting-websocket/ 48 | * - Joe Walnes 49 | */ 50 | function ReconnectingWebSocket(url, protocols) { 51 | protocols = protocols || []; 52 | 53 | // These can be altered by calling code. 54 | this.debug = false; 55 | this.reconnectInterval = 1000; 56 | this.timeoutInterval = 2000; 57 | 58 | var self = this; 59 | var ws; 60 | var forcedClose = false; 61 | var timedOut = false; 62 | 63 | this.url = url; 64 | this.protocols = protocols; 65 | this.readyState = WebSocket.CONNECTING; 66 | this.URL = url; // Public API 67 | 68 | this.onopen = function(event) { 69 | }; 70 | 71 | this.onclose = function(event) { 72 | }; 73 | 74 | this.onconnecting = function(event) { 75 | }; 76 | 77 | this.onmessage = function(event) { 78 | }; 79 | 80 | this.onerror = function(event) { 81 | }; 82 | 83 | function connect(reconnectAttempt) { 84 | ws = new WebSocket(url, protocols); 85 | 86 | self.onconnecting(); 87 | if (self.debug || ReconnectingWebSocket.debugAll) { 88 | console.debug('ReconnectingWebSocket', 'attempt-connect', url); 89 | } 90 | 91 | var localWs = ws; 92 | var timeout = setTimeout(function() { 93 | if (self.debug || ReconnectingWebSocket.debugAll) { 94 | console.debug('ReconnectingWebSocket', 'connection-timeout', url); 95 | } 96 | timedOut = true; 97 | localWs.close(); 98 | timedOut = false; 99 | }, self.timeoutInterval); 100 | 101 | ws.onopen = function(event) { 102 | clearTimeout(timeout); 103 | if (self.debug || ReconnectingWebSocket.debugAll) { 104 | console.debug('ReconnectingWebSocket', 'onopen', url); 105 | } 106 | self.readyState = WebSocket.OPEN; 107 | reconnectAttempt = false; 108 | self.onopen(event); 109 | }; 110 | 111 | ws.onclose = function(event) { 112 | clearTimeout(timeout); 113 | ws = null; 114 | if (forcedClose) { 115 | self.readyState = WebSocket.CLOSED; 116 | self.onclose(event); 117 | } else { 118 | self.readyState = WebSocket.CONNECTING; 119 | self.onconnecting(); 120 | if (!reconnectAttempt && !timedOut) { 121 | if (self.debug || ReconnectingWebSocket.debugAll) { 122 | console.debug('ReconnectingWebSocket', 'onclose', url); 123 | } 124 | self.onclose(event); 125 | } 126 | setTimeout(function() { 127 | connect(true); 128 | }, self.reconnectInterval); 129 | } 130 | }; 131 | ws.onmessage = function(event) { 132 | if (self.debug || ReconnectingWebSocket.debugAll) { 133 | console.debug('ReconnectingWebSocket', 'onmessage', url, event.data); 134 | } 135 | self.onmessage(event); 136 | }; 137 | ws.onerror = function(event) { 138 | if (self.debug || ReconnectingWebSocket.debugAll) { 139 | console.debug('ReconnectingWebSocket', 'onerror', url, event); 140 | } 141 | self.onerror(event); 142 | }; 143 | } 144 | connect(url); 145 | 146 | this.send = function(data) { 147 | if (ws) { 148 | if (self.debug || ReconnectingWebSocket.debugAll) { 149 | console.debug('ReconnectingWebSocket', 'send', url, data); 150 | } 151 | return ws.send(data); 152 | } else { 153 | throw 'INVALID_STATE_ERR : Pausing to reconnect websocket'; 154 | } 155 | }; 156 | 157 | this.close = function() { 158 | if (ws) { 159 | forcedClose = true; 160 | ws.close(); 161 | } 162 | }; 163 | 164 | /** 165 | * Additional public API method to refresh the connection if still open (close, re-open). 166 | * For example, if the app suspects bad data / missed heart beats, it can try to refresh. 167 | */ 168 | this.refresh = function() { 169 | if (ws) { 170 | ws.close(); 171 | } 172 | }; 173 | } 174 | 175 | /** 176 | * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. 177 | */ 178 | ReconnectingWebSocket.debugAll = false; 179 | 180 | -------------------------------------------------------------------------------- /web/js/server.js: -------------------------------------------------------------------------------- 1 | var Server = require('ws').Server; 2 | var iosDevice = require('node-ios-device'); 3 | var exec = require('child_process').exec, child; 4 | 5 | var port = process.env.PORT || 9030; 6 | var ws = new Server({port: port}); 7 | var clients = [ ]; /* List of all connected clients */ 8 | var devices = [ ]; /* Lsit of all devices and theirs status */ 9 | var ports = [ ]; /* List of ports for devices connection and theirs status*/ 10 | 11 | var portFrom = 9231; 12 | var portTo = 9299; 13 | 14 | console.log('============= START =============='); 15 | 16 | function getRandomInt(min, max) { 17 | return Math.floor(Math.random() * (max - min)) + min; 18 | } 19 | 20 | function updateDevicesInformation() { 21 | clients.forEach(function (client) { 22 | if (client['type'] == 'landingpage') { 23 | client.send(JSON.stringify({'action': 'deviceslist', 'data': devices})); 24 | console.log('-> Sent updated information about devices list to landing pages'); 25 | } 26 | }); 27 | } 28 | 29 | function findPort(udid) { 30 | let port = getRandomInt(portFrom, portTo); 31 | let effort = 0; 32 | 33 | console.log('Effort ' + effort + ' and port is ' + port); 34 | 35 | while ((port in ports)&&(effort < 10)) { 36 | port = getRandomInt(portFrom, portTo); 37 | effort++; 38 | console.log(port + ' - ' + effort); 39 | } 40 | 41 | if (effort >= 10 ) { 42 | return 'Didn\'t find free ports from ' + portFrom + ' to ' + portTo; 43 | }else { 44 | ports[port] = port; 45 | ports[port]['status'] = 'busy'; 46 | ports[port]['udid'] = udid; 47 | 48 | return port; 49 | } 50 | } 51 | 52 | function startAppium() { 53 | let command = 'appium'; 54 | exec(command, 55 | function (error, stdout, stderr) { 56 | if (error !== null) { 57 | console.log('exec error: ' + error); 58 | } 59 | } 60 | ); 61 | console.log('Appium was started'); 62 | } 63 | 64 | iosDevice 65 | .trackDevices() 66 | .on('devices', function (list_of_devices) { 67 | if (devices.length == 0) { 68 | devices = list_of_devices; 69 | devices.forEach(function (device) { 70 | device['status'] = 'free'; 71 | }) 72 | }else { 73 | let position = 0; 74 | devices.forEach(function (device) { 75 | if (!(device in list_of_devices)) { 76 | devices.splice(position, 1); 77 | } 78 | position ++; 79 | }) 80 | list_of_devices.forEach(function (device) { 81 | if (!(device in devices)) { 82 | devices.push(device); 83 | devices.forEach(function (item) { 84 | if (item['status'] !== undefined) item['status'] = 'free'; 85 | }) 86 | } 87 | }); 88 | } 89 | updateDevicesInformation(); 90 | }) 91 | .on('error', function (err) { 92 | console.error('Error!', err); 93 | }); 94 | 95 | ws.on('connection', function(w, req){ 96 | 97 | var string__from_connection = req.url.replace('/:', ''); 98 | var data__from_connection = string__from_connection.split('/'); 99 | var userID = data__from_connection[0].toString(); 100 | 101 | clients[userID] = w; 102 | clients[userID]["type"] = data__from_connection[1]; 103 | 104 | console.log('Connected: UserID [' + userID + '] with _' + clients[userID]["type"] + '_ type'); 105 | 106 | if (clients[userID]["type"] == 'landingpage') { 107 | let data = [ ]; 108 | 109 | devices.forEach(function (device){ 110 | console.log(device); 111 | data.push({"udid": device["udid"], "name": device["name"], "ios": device["productVersion"], "deviceclass": device["deviceClass"]}) 112 | }); 113 | 114 | clients[userID].send(JSON.stringify({"action": "deviceslist", "data": data})); 115 | console.log('There is Landing page and SERVER transmited devices\'s list') 116 | } 117 | 118 | w.on('message', function(message){ 119 | let path = 'PATH TO YOUR FOLDER'; 120 | console.log(' -> Received from ' + userID + ': ' + message); 121 | 122 | var messageArray = JSON.parse(message) 123 | var toUserWebSocket = clients[messageArray["toudid"]] 124 | 125 | switch (messageArray['whoyouare']) { 126 | case 'device': 127 | if (toUserWebSocket) { 128 | messageArray["toudid"] = userID 129 | toUserWebSocket.send(JSON.stringify(messageArray)) 130 | console.log('-> Sent to ' + messageArray["toudid"] + ': ' + JSON.stringify(messageArray)) 131 | } 132 | if(messageArray["action"] == 'deviceconnected') { 133 | clients[userID]['udid'] = messageArray["udid"]; 134 | } 135 | if(messageArray["action"] == 'starttests') { 136 | let device = devices.find(obj => obj["udid"] == messageArray['udid']); 137 | let command = 'sh ' + path + '/starttests.sh ' + messageArray['udid'] + ' ' + device["name"]; 138 | console.log('START PYTHON TESTS starttests.SH'); 139 | console.log('Ready to exec = ' + command); 140 | exec(command, {maxBuffer: 1024 * 500}, 141 | function (error, stdout, stderr) { 142 | console.log('RUN from NODE'); 143 | console.log('stdout: ' + stdout); 144 | console.log('stderr: ' + stderr); 145 | if (error !== null) { 146 | console.log('exec error: ' + error); 147 | } 148 | } 149 | ); 150 | } 151 | break; 152 | case 'landingpage': 153 | if(messageArray["action"] == "run") { 154 | let port = findPort(messageArray['udid']); 155 | let message_type_for_port = typeof port; 156 | 157 | if (message_type_for_port !== 'string') { 158 | let command = 'websocketd --port=' + port + ' --staticdir=' + path + '/web sh ' + path + '/smile.sh ' + messageArray['udid'] + ' ' + messageArray['name']; 159 | console.log('START SMILE.SH'); 160 | console.log('Ready to exec = ' + command); 161 | exec(command, {maxBuffer: 1024 * 500}, 162 | function (error, stdout, stderr) { 163 | console.log('stdout: ' + stdout); 164 | console.log('stderr: ' + stderr); 165 | if (error !== null) { 166 | console.log('exec error: ' + error); 167 | } 168 | } 169 | ); 170 | 171 | devices.forEach(function (device) { 172 | if (device["udid"] == messageArray['udid']) { 173 | device["status"] = 'busy'; 174 | device["port"] = port; 175 | } 176 | }); 177 | updateDevicesInformation(); 178 | w.send(JSON.stringify({'action': 'open', 'url': 'http://localhost:' + port + '/?' + messageArray['udid']})); 179 | } 180 | } 181 | break; 182 | case 'commuter': 183 | console.log("Commuter SEND"); 184 | if(messageArray["action"] == 'deviceready') { 185 | clients.forEach(function (client) { 186 | if (client["type"] == 'device') { 187 | console.log('FOUND device'); 188 | } 189 | if ((client["type"] == 'device')&&(client["udid"] == messageArray["udid"])) { 190 | client.send(JSON.stringify({'action': messageArray["action"], "udid": messageArray["udid"]})); 191 | } 192 | }); 193 | } 194 | break; 195 | } 196 | 197 | }); 198 | 199 | w.on('close', function() { 200 | devices.forEach(function (device) { 201 | if (device["udid"] == clients[userID]["udid"]) { 202 | device["status"] = 'free'; 203 | let port = device["port"]; 204 | delete device["port"]; 205 | } 206 | }); 207 | clients[userID].send(JSON.stringify({'action': 'free', 'udid': userID})); 208 | console.log('Deleted: Client ID ' + userID + ' with type ' + clients[userID]["type"]); 209 | updateDevicesInformation(); 210 | delete clients[userID]; 211 | delete ports[port]; 212 | }); 213 | 214 | }); 215 | 216 | startAppium(); -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 330 | 331 | 332 | 333 | 334 | 335 | 336 |
337 | 350 | 388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /web/js/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.4.0 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.0",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ae(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ne(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ne(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n=void 0,r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n}else r&&(Q.set(this,i,k.event.trigger(k.extend(r.shift(),k.Event.prototype),r,this)),e.stopImmediatePropagation())}})):k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0