├── .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 |
--------------------------------------------------------------------------------
/web/img/home.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |

347 |
348 |
349 |
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=/