robot-appium-vision 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .keywords import AppiumKeywords
2
+
3
+ __all__ = ["AppiumKeywords"]
appium_vision/cli.py ADDED
@@ -0,0 +1,55 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import venv
5
+
6
+
7
+ def _venv_python(venv_dir):
8
+ """Return python executable path inside venv."""
9
+ if os.name == "nt":
10
+ return os.path.join(venv_dir, "Scripts", "python.exe")
11
+ return os.path.join(venv_dir, "bin", "python")
12
+
13
+
14
+ def setup():
15
+ """
16
+ Creates a virtual environment and installs robot-appium-vision into it.
17
+ """
18
+ venv_dir = ".venv"
19
+
20
+ if not os.path.exists(venv_dir):
21
+ print("📦 Creating virtual environment...")
22
+ venv.create(venv_dir, with_pip=True)
23
+ else:
24
+ print("ℹ️ Virtual environment already exists")
25
+
26
+ python = _venv_python(venv_dir)
27
+
28
+ print("📥 Installing robot-appium-vision into virtual environment...")
29
+ subprocess.check_call([
30
+ python, "-m", "pip", "install", "--upgrade", "pip"
31
+ ])
32
+ subprocess.check_call([
33
+ python, "-m", "pip", "install", "robot-appium-vision"
34
+ ])
35
+
36
+ print("\n✅ Setup complete!")
37
+ print("\nNext steps:")
38
+ if os.name == "nt":
39
+ print(r" Activate venv: .\.venv\Scripts\Activate.ps1")
40
+ else:
41
+ print(" Activate venv: source .venv/bin/activate")
42
+
43
+
44
+ def main():
45
+ if len(sys.argv) < 2:
46
+ print("Usage: appium-vision setup")
47
+ sys.exit(1)
48
+
49
+ command = sys.argv[1].lower()
50
+
51
+ if command == "setup":
52
+ setup()
53
+ else:
54
+ print(f"Unknown command: {command}")
55
+ print("Available commands: setup")
@@ -0,0 +1,520 @@
1
+ from appium import webdriver
2
+ from robot.api.deco import keyword
3
+ from appium.options.android import UiAutomator2Options
4
+ from robot.api import logger
5
+ from robot.libraries.BuiltIn import BuiltIn
6
+ from datetime import datetime
7
+ import subprocess
8
+ import configparser
9
+ import time
10
+ import os
11
+ import json
12
+ import cv2
13
+ import shutil
14
+ import importlib.util
15
+ import pytesseract
16
+
17
+
18
+ class AppiumKeywords:
19
+ """
20
+ Robot Framework keyword library for Android automation using Appium.
21
+
22
+ Capabilities:
23
+ - Multi-DUT Appium session management
24
+ - OCR-based text verification and tapping
25
+ - Image-based verification and clicking using OpenCV
26
+ - Coordinate-based tap actions
27
+ - Android shell command execution
28
+ - Safe swipe and scroll gestures
29
+ - Screen recording with embedded video reporting
30
+ """
31
+
32
+ def __init__(self):
33
+ """
34
+ Initializes keyword library.
35
+ - Loads DUT configuration from configurations.ini
36
+ - Initializes driver dictionary
37
+ """
38
+ self.drivers = {}
39
+
40
+ base_path = os.path.dirname(os.path.abspath(__file__))
41
+ ini_path = os.path.join(base_path, "..", "Configurations", "configurations.ini")
42
+
43
+ self.config = configparser.ConfigParser()
44
+ self.config.read(ini_path)
45
+
46
+ # Optional Tesseract override (recommended for PyPI users)
47
+ tesseract_cmd = os.getenv("TESSERACT_CMD")
48
+ if tesseract_cmd:
49
+ pytesseract.pytesseract.tesseract_cmd = tesseract_cmd
50
+
51
+ # Runtime dependency validation
52
+ try:
53
+ self._check_runtime_dependencies()
54
+ except Exception as e:
55
+ logger.warn(
56
+ f"⚠️ Dependency check skipped during initialization:\n{e}"
57
+ )
58
+
59
+ # ---------------------------------------------------------------------
60
+ @keyword
61
+ def get_device_id(self, dut_name):
62
+ """
63
+ Returns DUT configuration section for the given DUT name.
64
+
65
+ Arguments:
66
+ - dut_name (str): Logical DUT name (Phone / Main / Cluster)
67
+
68
+ Returns:
69
+ - Config section containing device capabilities
70
+
71
+ Fails If:
72
+ - DUT section is not found
73
+ """
74
+ section = f"DUT.{dut_name}"
75
+ if section not in self.config:
76
+ raise Exception(f"DUT section '{section}' not found")
77
+ return self.config[section]
78
+
79
+ # ---------------------------------------------------------------------
80
+ @keyword
81
+ def start_appium_session(self, dut_name):
82
+ """
83
+ Starts or reuses an Appium session for the given DUT.
84
+
85
+ Maintains one Appium driver per DUT.
86
+
87
+ Arguments:
88
+ - dut_name (str): Logical DUT name
89
+
90
+ Returns:
91
+ - Appium WebDriver instance
92
+ """
93
+ if dut_name in self.drivers:
94
+ return self.drivers[dut_name]
95
+
96
+ caps = self.get_device_id(dut_name)
97
+ options = UiAutomator2Options().load_capabilities(caps)
98
+
99
+ driver = webdriver.Remote(
100
+ command_executor="http://127.0.0.1:4723",
101
+ options=options
102
+ )
103
+
104
+ self.drivers[dut_name] = driver
105
+ return driver
106
+
107
+ # ---------------------------------------------------------------------
108
+ @keyword
109
+ def stop_appium_session(self):
110
+ """
111
+ Stops all active Appium sessions.
112
+ """
113
+ for driver in self.drivers.values():
114
+ driver.quit()
115
+ self.drivers.clear()
116
+
117
+ # ---------------------------------------------------------------------
118
+ @keyword
119
+ def verify_text_appium_full(self, expected_text, dut_name):
120
+ """
121
+ Verifies that exact visible text is present on screen using Appium.
122
+
123
+ Arguments:
124
+ - expected_text (str): Exact text to verify
125
+ - dut_name (str): Logical DUT name
126
+
127
+ Returns:
128
+ - True if text is found
129
+
130
+ Fails If:
131
+ - Text is not present
132
+ """
133
+ driver = self.start_appium_session(dut_name)
134
+
135
+ elements = driver.find_elements(
136
+ by="xpath",
137
+ value="//*[normalize-space(@text) != '']"
138
+ )
139
+
140
+ visible_texts = [el.text.strip() for el in elements if el.text.strip()]
141
+
142
+ if expected_text in visible_texts:
143
+ logger.info(f"<b style='color:green'>Text verified:</b> {expected_text}", html=True)
144
+ return True
145
+
146
+ raise AssertionError(f"Exact text '{expected_text}' not found")
147
+
148
+ # ---------------------------------------------------------------------
149
+ @keyword
150
+ def tap_by_coordinates(self, json_name, key_name, dut_name):
151
+ """
152
+ Taps on screen using X,Y coordinates from a JSON file.
153
+
154
+ Arguments:
155
+ - json_name (str): JSON file name
156
+ - key_name (str): Key containing x,y values
157
+ - dut_name (str): Logical DUT name
158
+
159
+ Returns:
160
+ - Success message
161
+ """
162
+ driver = self.start_appium_session(dut_name)
163
+ project_root = BuiltIn().get_variable_value("${EXECDIR}")
164
+
165
+ json_file = os.path.join(project_root, "Resources", "Coordinates", json_name)
166
+ if not os.path.isfile(json_file):
167
+ raise AssertionError(f"JSON file not found: {json_file}")
168
+
169
+ with open(json_file) as f:
170
+ data = json.load(f)
171
+
172
+ if key_name not in data:
173
+ raise AssertionError(f"Key '{key_name}' not found")
174
+
175
+ x = int(data[key_name]["x"])
176
+ y = int(data[key_name]["y"])
177
+
178
+ driver.execute_script("mobile: clickGesture", {"x": x, "y": y})
179
+ return f"Tapped at ({x},{y}) on {dut_name}"
180
+
181
+ # ---------------------------------------------------------------------
182
+ @keyword
183
+ def tap_by_text(self, expected_text, dut_name):
184
+ """
185
+ Taps on visible text using OCR instead of UI hierarchy.
186
+
187
+ Arguments:
188
+ - expected_text (str): Text to tap
189
+ - dut_name (str): Logical DUT name
190
+
191
+ Returns:
192
+ - True if text is tapped
193
+
194
+ Fails If:
195
+ - Text not found via OCR
196
+ """
197
+ driver = self.start_appium_session(dut_name)
198
+ output_dir = BuiltIn().get_variable_value("${OUTPUTDIR}")
199
+ screenshot_path = os.path.join(output_dir, "ocr_screen.png")
200
+
201
+ driver.save_screenshot(screenshot_path)
202
+ img = cv2.imread(screenshot_path)
203
+
204
+ ocr_data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)
205
+
206
+ for i, text in enumerate(ocr_data["text"]):
207
+ if text.strip() == expected_text:
208
+ x = ocr_data["left"][i]
209
+ y = ocr_data["top"][i]
210
+ w = ocr_data["width"][i]
211
+ h = ocr_data["height"][i]
212
+
213
+ driver.execute_script(
214
+ "mobile: clickGesture",
215
+ {"x": int(x + w / 2), "y": int(y + h / 2)}
216
+ )
217
+ return True
218
+
219
+ raise AssertionError(f"Text '{expected_text}' not found via OCR")
220
+
221
+ # ---------------------------------------------------------------------
222
+ @keyword
223
+ def verify_image_element(self, image_name, dut_name, threshold=0.9):
224
+ """
225
+ Verifies an image on screen using OpenCV template matching.
226
+
227
+ Arguments:
228
+ - image_name (str): Reference image
229
+ - dut_name (str): Logical DUT name
230
+ - threshold (float): Similarity threshold
231
+
232
+ Returns:
233
+ - True if image is matched
234
+ """
235
+ driver = self.start_appium_session(dut_name)
236
+ project_root = BuiltIn().get_variable_value("${EXECDIR}")
237
+ output_dir = BuiltIn().get_variable_value("${OUTPUTDIR}")
238
+
239
+ ref_img = cv2.imread(os.path.join(project_root, "Resources", "images", image_name))
240
+ screenshot_path = os.path.join(output_dir, f"verify_{time.time()}.png")
241
+ driver.save_screenshot(screenshot_path)
242
+
243
+ screen = cv2.imread(screenshot_path)
244
+ res = cv2.matchTemplate(
245
+ cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY),
246
+ cv2.cvtColor(ref_img, cv2.COLOR_BGR2GRAY),
247
+ cv2.TM_CCOEFF_NORMED
248
+ )
249
+
250
+ _, max_val, _, _ = cv2.minMaxLoc(res)
251
+ if max_val >= threshold:
252
+ return True
253
+
254
+ raise AssertionError(f"Image match failed: score={max_val:.3f}")
255
+
256
+ # ---------------------------------------------------------------------
257
+ @keyword
258
+ def click_by_image(self, image_name, dut_name, threshold=0.8):
259
+ """
260
+ Clicks on UI element using image recognition.
261
+
262
+ Arguments:
263
+ - image_name (str): Reference image
264
+ - dut_name (str): Logical DUT name
265
+ - threshold (float): Confidence threshold
266
+
267
+ Returns:
268
+ - Click success message
269
+ """
270
+ driver = self.start_appium_session(dut_name)
271
+ project_root = BuiltIn().get_variable_value("${EXECDIR}")
272
+ output_dir = BuiltIn().get_variable_value("${OUTPUTDIR}")
273
+
274
+ ref = cv2.imread(os.path.join(project_root, "Resources", "images", image_name))
275
+ screenshot = os.path.join(output_dir, f"click_{time.time()}.png")
276
+ driver.save_screenshot(screenshot)
277
+
278
+ screen = cv2.imread(screenshot)
279
+ res = cv2.matchTemplate(
280
+ cv2.cvtColor(screen, cv2.COLOR_BGR2GRAY),
281
+ cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY),
282
+ cv2.TM_CCOEFF_NORMED
283
+ )
284
+
285
+ _, max_val, _, max_loc = cv2.minMaxLoc(res)
286
+ if max_val < threshold:
287
+ raise AssertionError("Image not found")
288
+
289
+ h, w = ref.shape[:2]
290
+ x = max_loc[0] + w // 2
291
+ y = max_loc[1] + h // 2
292
+
293
+ driver.execute_script("mobile: clickGesture", {"x": x, "y": y})
294
+ return f"Clicked image at ({x},{y})"
295
+
296
+ # ---------------------------------------------------------------------
297
+ @keyword
298
+ def run_command(self, command, dut_name, timeout_ms=5000):
299
+ """
300
+ Executes Android shell command using Appium.
301
+
302
+ Arguments:
303
+ - command (str): Shell command
304
+ - dut_name (str): Logical DUT name
305
+ - timeout_ms (int): Timeout
306
+
307
+ Returns:
308
+ - Command output
309
+ """
310
+ driver = self.start_appium_session(dut_name)
311
+ parts = command.split()
312
+
313
+ result = driver.execute_script(
314
+ "mobile: shell",
315
+ {"command": parts[0], "args": parts[1:], "timeout": timeout_ms}
316
+ )
317
+
318
+ if isinstance(result, dict):
319
+ return result.get("stdout", "").strip()
320
+ return result.strip()
321
+
322
+ # ---------------------------------------------------------------------
323
+ @keyword
324
+ def press_key(self, keycode, dut_name):
325
+ """
326
+ Presses Android hardware/system key.
327
+
328
+ Arguments:
329
+ - keycode (int): Android keycode
330
+ - dut_name (str): Logical DUT name
331
+ """
332
+ driver = self.start_appium_session(dut_name)
333
+ driver.execute_script(
334
+ "mobile: shell",
335
+ {"command": "input", "args": ["keyevent", str(keycode)]}
336
+ )
337
+
338
+ # ---------------------------------------------------------------------
339
+ @keyword
340
+ def swipe_left_right(self, dut_name, direction="left", percent=0.9):
341
+ """
342
+ Performs safe horizontal swipe.
343
+
344
+ Arguments:
345
+ - dut_name (str): Logical DUT name
346
+ - direction (str): left / right
347
+ - percent (float): Swipe distance
348
+ """
349
+ driver = self.start_appium_session(dut_name)
350
+ size = driver.get_window_size()
351
+
352
+ driver.execute_script(
353
+ "mobile: scrollGesture",
354
+ {
355
+ "direction": direction,
356
+ "percent": percent,
357
+ "left": int(size["width"] * 0.1),
358
+ "top": int(size["height"] * 0.35),
359
+ "width": int(size["width"] * 0.8),
360
+ "height": int(size["height"] * 0.3),
361
+ }
362
+ )
363
+
364
+ # ---------------------------------------------------------------------
365
+ @keyword
366
+ def scroll_top_bottom(self, dut_name, direction="down", percent=0.9):
367
+ """
368
+ Performs safe vertical scroll.
369
+
370
+ Arguments:
371
+ - dut_name (str): Logical DUT name
372
+ - direction (str): up / down
373
+ - percent (float): Scroll distance
374
+ """
375
+ driver = self.start_appium_session(dut_name)
376
+ size = driver.get_window_size()
377
+
378
+ driver.execute_script(
379
+ "mobile: scrollGesture",
380
+ {
381
+ "direction": direction,
382
+ "percent": percent,
383
+ "left": int(size["width"] * 0.1),
384
+ "top": int(size["height"] * 0.15),
385
+ "width": int(size["width"] * 0.8),
386
+ "height": int(size["height"] * 0.7),
387
+ }
388
+ )
389
+
390
+ # ---------------------------------------------------------------------
391
+ @keyword
392
+ def start_screen_recording(self, dut_name, test_name):
393
+ """
394
+ Starts Android screen recording using adb.
395
+
396
+ Arguments:
397
+ - dut_name (str): Logical DUT name
398
+ - test_name (str): Test case name
399
+
400
+ Returns:
401
+ - Device video path
402
+ """
403
+ device_id = self.get_device_id(dut_name).get("device_id")
404
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
405
+ path = f"/sdcard/{device_id}_{timestamp}_{test_name}.mp4"
406
+
407
+ self._screen_proc = subprocess.Popen(
408
+ ["adb", "-s", device_id, "shell", "screenrecord", path],
409
+ stdout=subprocess.DEVNULL,
410
+ stderr=subprocess.DEVNULL
411
+ )
412
+
413
+ self._device_video_path = path
414
+ self._device_id = device_id
415
+ return path
416
+
417
+ # ---------------------------------------------------------------------
418
+ @keyword
419
+ def stop_screen_recording(self, dut_name, local_video_path):
420
+ """
421
+ Stops screen recording and pulls video to local system.
422
+
423
+ Arguments:
424
+ - dut_name (str): Logical DUT name
425
+ - local_video_path (str): Local save path
426
+
427
+ Returns:
428
+ - Local video path
429
+ """
430
+ if self._screen_proc:
431
+ self._screen_proc.terminate()
432
+ self._screen_proc.wait()
433
+
434
+ subprocess.run(
435
+ ["adb", "-s", self._device_id, "pull", self._device_video_path, local_video_path],
436
+ check=True
437
+ )
438
+ return local_video_path
439
+
440
+ # ---------------------------------------------------------------------
441
+ @keyword
442
+ def Test_Video(self, video_path, width=480, title="Screen Recording"):
443
+ """
444
+ Embeds video in Robot Framework HTML report.
445
+
446
+ Arguments:
447
+ - video_path (str): MP4 file path
448
+ - width (int): Video width
449
+ - title (str): Video title
450
+ """
451
+ if not os.path.exists(video_path):
452
+ logger.warn(f"Video not found: {video_path}")
453
+ return
454
+
455
+ html = f"""
456
+ <b>{title}</b><br>
457
+ <video width="{width}" controls>
458
+ <source src="{video_path}" type="video/mp4">
459
+ </video>
460
+ """
461
+ logger.info(html, html=True)
462
+
463
+
464
+ def _check_runtime_dependencies(self):
465
+ """
466
+ Performs runtime dependency checks.
467
+
468
+ - Verifies required Python packages
469
+ - Verifies optional system tools (adb, tesseract)
470
+ - Logs warnings instead of crashing where possible
471
+ """
472
+
473
+ # -------------------------------
474
+ # Required Python packages
475
+ # -------------------------------
476
+ required_packages = {
477
+ "robotframework": "robot",
478
+ "appium-python-client": "appium",
479
+ "selenium": "selenium",
480
+ "opencv-python": "cv2",
481
+ "pytesseract": "pytesseract",
482
+ }
483
+
484
+ for pkg_name, import_name in required_packages.items():
485
+ if importlib.util.find_spec(import_name) is None:
486
+ raise RuntimeError(
487
+ f"❌ Required dependency '{pkg_name}' is not installed.\n"
488
+ f"Install it using: pip install {pkg_name}"
489
+ )
490
+
491
+ logger.info("✅ All required Python dependencies are installed")
492
+
493
+ # -------------------------------
494
+ # adb check (required for shell & recording)
495
+ # -------------------------------
496
+ if shutil.which("adb") is None:
497
+ logger.warn(
498
+ "⚠️ adb not found in PATH.\n"
499
+ "Keywords using shell commands and screen recording may fail."
500
+ )
501
+ else:
502
+ logger.info("✅ adb detected")
503
+
504
+ # -------------------------------
505
+ # Tesseract OCR check (optional)
506
+ # -------------------------------
507
+ tesseract_path = (
508
+ os.getenv("TESSERACT_CMD")
509
+ or shutil.which("tesseract")
510
+ )
511
+
512
+ if not tesseract_path:
513
+ logger.warn(
514
+ "⚠️ Tesseract OCR not found.\n"
515
+ "OCR-based keywords (Tap By Text) will NOT work.\n"
516
+ "Install Tesseract and set TESSERACT_CMD environment variable."
517
+ )
518
+ else:
519
+ pytesseract.pytesseract.tesseract_cmd = tesseract_path
520
+ logger.info(f"✅ Tesseract OCR detected at: {tesseract_path}")
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: robot-appium-vision
3
+ Version: 0.1.2
4
+ Summary: Robot Framework Appium keyword library with OCR and image-based actions
5
+ Author-email: Khajavali <dudekulakhaja786@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Khajavali
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/khaja786431/robot-appium-vision
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Framework :: Robot Framework
31
+ Classifier: Topic :: Software Development :: Testing
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Requires-Python: >=3.10
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: robotframework
37
+ Requires-Dist: appium-python-client
38
+ Requires-Dist: selenium
39
+ Requires-Dist: opencv-python
40
+ Requires-Dist: pytesseract
41
+ Dynamic: license-file
42
+
43
+ # Robot Appium Vision Library
44
+
45
+ Advanced Robot Framework keyword library for Appium automation with:
46
+ - OCR-based text detection
47
+ - Image-based verification and clicking
48
+ - Coordinate tapping
49
+ - Safe scroll & swipe
50
+ - Android shell commands
51
+ - Screen recording with video embedding
52
+
53
+ ## Installation
54
+
55
+ pip install robot-appium-vision
56
+
57
+
58
+ ## Installation
59
+
60
+ *** Settings ***
61
+ Library AppiumKeywords
62
+
63
+ *** Test Cases ***
64
+ Verify Text
65
+ Verify Text Appium Full Settings Phone
66
+
67
+ ## OCR Setup (Windows)
68
+ set TESSERACT_CMD=C:\Program Files\Tesseract-OCR\tesseract.exe
69
+
70
+
71
+ ## Dependencies
72
+ - Appium Server
73
+ - Android device / emulator
74
+ - OpenCV
75
+ - Tesseract OCR
@@ -0,0 +1,9 @@
1
+ appium_vision/__init__.py,sha256=wpxfNgRSeFJ5QJ2q7XypR88onVWUD3URV7lcInZI9Ug,70
2
+ appium_vision/cli.py,sha256=Pzt3WAkoXBVv_FuxM6CeCqoighTrKBgBtPpkgw0dLOg,1471
3
+ appium_vision/keywords.py,sha256=itPnbee0fbizDFQiK-g3tw-edOtWUgF0GDwNCZT4kI8,17173
4
+ robot_appium_vision-0.1.2.dist-info/licenses/LICENSE,sha256=kFvwPgAMa_78-0tCdfeYtYnMzE-sHKzFZWOqwfn7d7s,1087
5
+ robot_appium_vision-0.1.2.dist-info/METADATA,sha256=IwZ3LPgkOrHQjvQbelqojyLwhVJbRQQN8zLzRWr0YUA,2680
6
+ robot_appium_vision-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ robot_appium_vision-0.1.2.dist-info/entry_points.txt,sha256=AbE8XLiapFO_BDhAexO9FTWCGY4T0qNAWdnnQNyUYOk,57
8
+ robot_appium_vision-0.1.2.dist-info/top_level.txt,sha256=7-hJE9FsccdYlLxmVdXwwC_MjKHRCvswV7NsJyiXjRc,14
9
+ robot_appium_vision-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ appium-vision = appium_vision.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Khajavali
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ appium_vision