robotframework-appiumwindows 0.1.0__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,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ import inspect
3
+ import functools
4
+
5
+ # Internal/private marker attribute names
6
+ _RUN_ON_FAILURE_ATTR = "__rof_wrapped__"
7
+ _IGNORE_RUN_FAILURE_ATTR = "__rof_ignore__"
8
+ _ORIGINAL_METHOD_ATTR = "__original__"
9
+
10
+
11
+ def _run_on_failure_decorator(method):
12
+ """Decorator to wrap keyword methods with _run_on_failure support."""
13
+ if getattr(method, _RUN_ON_FAILURE_ATTR, False):
14
+ # Already decorated → skip re-wrapping
15
+ return method
16
+
17
+ @functools.wraps(method)
18
+ def wrapper(*args, **kwargs):
19
+ try:
20
+ return method(*args, **kwargs)
21
+ except Exception:
22
+ self = args[0] if args else None
23
+ if self and hasattr(self, "_run_on_failure"):
24
+ self._run_on_failure()
25
+ raise
26
+
27
+ setattr(wrapper, _RUN_ON_FAILURE_ATTR, True) # mark as decorated
28
+ setattr(wrapper, _ORIGINAL_METHOD_ATTR, method) # keep reference to original
29
+ return wrapper
30
+
31
+
32
+ def ignore_on_fail(method):
33
+ """Decorator to mark methods that should never be wrapped by run_on_failure."""
34
+ setattr(method, _IGNORE_RUN_FAILURE_ATTR, True)
35
+ return method
36
+
37
+
38
+ class KeywordGroupMetaClass(type):
39
+ def __new__(cls, clsname, bases, attrs):
40
+ for name, method in list(attrs.items()):
41
+ if (
42
+ not name.startswith('_')
43
+ and inspect.isfunction(method)
44
+ and not getattr(method, _IGNORE_RUN_FAILURE_ATTR, False)
45
+ and not getattr(method, _RUN_ON_FAILURE_ATTR, False)
46
+ ):
47
+ attrs[name] = _run_on_failure_decorator(method)
48
+ return super().__new__(cls, clsname, bases, attrs)
49
+
50
+
51
+ class KeywordGroup(metaclass=KeywordGroupMetaClass):
52
+
53
+ def _invoke_original(self, method, *args, **kwargs):
54
+ """
55
+ Call the original (undecorated) implementation of a method.
56
+
57
+ Accepts either:
58
+ - method name (str), e.g. self._invoke_original("click", el)
59
+ - bound method itself, e.g. self._invoke_original(self.click, el)
60
+
61
+ Falls back to the current method if undecorated.
62
+ Returns None if method not found at all.
63
+ """
64
+ if isinstance(method, str):
65
+ method = getattr(self, method, None)
66
+ if method is None:
67
+ return None
68
+
69
+ original = getattr(method, _ORIGINAL_METHOD_ATTR, method)
70
+ return original(self, *args, **kwargs)
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .elementfinder import ElementFinder
4
+
5
+ __all__ = [
6
+ "ElementFinder",
7
+ ]
@@ -0,0 +1,264 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from AppiumLibrary import utils
4
+ from appium.webdriver.common.appiumby import AppiumBy
5
+ from robot.api import logger
6
+
7
+
8
+ class ElementFinder(object):
9
+
10
+ def __init__(self):
11
+ self._strategies = {
12
+ 'identifier': self._find_by_identifier,
13
+ 'id': self._find_by_id,
14
+ 'name': self._find_by_name,
15
+ 'xpath': self._find_by_xpath,
16
+ 'class': self._find_by_class_name,
17
+ 'accessibility_id': self._find_element_by_accessibility_id,
18
+ 'android': self._find_by_android,
19
+ 'viewtag': self._find_by_android_viewtag,
20
+ 'data_matcher': self._find_by_android_data_matcher,
21
+ 'view_matcher': self._find_by_android_view_matcher,
22
+ 'ios': self._find_by_ios,
23
+ 'css': self._find_by_css_selector,
24
+ 'jquery': self._find_by_sizzle_selector,
25
+ 'predicate': self._find_by_ios_predicate,
26
+ 'chain': self._find_by_chain,
27
+ 'default': self._find_by_default
28
+ }
29
+
30
+ def find(self, application, locator, tag=None):
31
+ assert application is not None
32
+ assert locator is not None and len(locator) > 0
33
+
34
+ (prefix, criteria) = self._parse_locator(locator)
35
+ prefix = 'default' if prefix is None else prefix
36
+ strategy = self._strategies.get(prefix)
37
+ if strategy is None:
38
+ raise ValueError("Element locator with prefix '" + prefix + "' is not supported")
39
+ (tag, constraints) = self._get_tag_and_constraints(tag)
40
+ return strategy(application, criteria, tag, constraints)
41
+
42
+ # Strategy routines, private
43
+
44
+ def _find_by_identifier(self, application, criteria, tag, constraints):
45
+ elements = self._normalize_result(application.find_elements(by=AppiumBy.ID, value=criteria))
46
+ elements.extend(self._normalize_result(application.find_elements(by=AppiumBy.NAME, value=criteria)))
47
+ return self._filter_elements(elements, tag, constraints)
48
+
49
+ def _find_by_id(self, application, criteria, tag, constraints):
50
+ # print(f"criteria is {criteria}")
51
+ return self._filter_elements(
52
+ application.find_elements(by=AppiumBy.ID, value=criteria),
53
+ tag, constraints)
54
+
55
+ def _find_by_name(self, application, criteria, tag, constraints):
56
+ return self._filter_elements(
57
+ application.find_elements(by=AppiumBy.NAME, value=criteria),
58
+ tag, constraints)
59
+
60
+ def _find_by_xpath(self, application, criteria, tag, constraints):
61
+ return self._filter_elements(
62
+ application.find_elements(by=AppiumBy.XPATH, value=criteria),
63
+ tag, constraints)
64
+
65
+ def _find_by_dom(self, application, criteria, tag, constraints):
66
+ result = application.execute_script("return %s;" % criteria)
67
+ if result is None:
68
+ return []
69
+ if not isinstance(result, list):
70
+ result = [result]
71
+ return self._filter_elements(result, tag, constraints)
72
+
73
+ def _find_by_sizzle_selector(self, application, criteria, tag, constraints):
74
+ js = "return jQuery('%s').get();" % criteria.replace("'", "\\'")
75
+ return self._filter_elements(
76
+ application.execute_script(js),
77
+ tag, constraints)
78
+
79
+ def _find_by_link_text(self, application, criteria, tag, constraints):
80
+ return self._filter_elements(
81
+ application.find_elements(by=AppiumBy.LINK_TEXT, value=criteria),
82
+ tag, constraints)
83
+
84
+ def _find_by_css_selector(self, application, criteria, tag, constraints):
85
+ return self._filter_elements(
86
+ application.find_elements(by=AppiumBy.CSS_SELECTOR, value=criteria),
87
+ tag, constraints)
88
+
89
+ def _find_by_tag_name(self, application, criteria, tag, constraints):
90
+ return self._filter_elements(
91
+ application.find_elements(by=AppiumBy.TAG_NAME, value=criteria),
92
+ tag, constraints)
93
+
94
+ def _find_by_class_name(self, application, criteria, tag, constraints):
95
+ return self._filter_elements(
96
+ application.find_elements(by=AppiumBy.CLASS_NAME, value=criteria),
97
+ tag, constraints)
98
+
99
+ def _find_element_by_accessibility_id(self, application, criteria, tag, constraints):
100
+ return self._filter_elements(
101
+ application.find_elements(by=AppiumBy.ACCESSIBILITY_ID, value=criteria),
102
+ tag, constraints)
103
+
104
+ def _find_by_android(self, application, criteria, tag, constraints):
105
+ """Find element matches by UI Automator."""
106
+ return self._filter_elements(
107
+ application.find_elements(by=AppiumBy.ANDROID_UIAUTOMATOR, value=criteria),
108
+ tag, constraints)
109
+
110
+ def _find_by_android_viewtag(self, application, criteria, tag, constraints):
111
+ """Find element matches by its view tag
112
+ Espresso only
113
+ """
114
+ return self._filter_elements(
115
+ application.find_elements(by=AppiumBy.ANDROID_VIEWTAG, value=criteria),
116
+ tag, constraints)
117
+
118
+ def _find_by_android_data_matcher(self, application, criteria, tag, constraints):
119
+ """Find element matches by Android Data Matcher
120
+ Espresso only
121
+ """
122
+ return self._filter_elements(
123
+ application.find_elements(by=AppiumBy.ANDROID_DATA_MATCHER, value=criteria),
124
+ tag, constraints)
125
+
126
+ def _find_by_android_view_matcher(self, application, criteria, tag, constraints):
127
+ """Find element matches by Android View Matcher
128
+ Espresso only
129
+ """
130
+ return self._filter_elements(
131
+ application.find_elements(by=AppiumBy.ANDROID_VIEW_MATCHER, value=criteria),
132
+ tag, constraints)
133
+
134
+ def _find_by_ios(self, application, criteria, tag, constraints):
135
+ """Find element matches by UI Automation."""
136
+ return self._filter_elements(
137
+ application.find_elements(by=AppiumBy.IOS_UIAUTOMATION, value=criteria),
138
+ tag, constraints)
139
+
140
+ def _find_by_ios_predicate(self, application, criteria, tag, constraints):
141
+ """Find element matches by iOSNsPredicateString."""
142
+ return self._filter_elements(
143
+ application.find_elements(by=AppiumBy.IOS_PREDICATE, value=criteria),
144
+ tag, constraints)
145
+
146
+ def _find_by_chain(self, application, criteria, tag, constraints):
147
+ """Find element matches by iOSChainString."""
148
+ return self._filter_elements(
149
+ application.find_elements(by=AppiumBy.IOS_CLASS_CHAIN, value=criteria),
150
+ tag, constraints)
151
+
152
+ def _find_by_default(self, application, criteria, tag, constraints):
153
+ if self._is_xpath(criteria):
154
+ return self._find_by_xpath(application, criteria, tag, constraints)
155
+ # Used `id` instead of _find_by_key_attrs since iOS and Android internal `id` alternatives are
156
+ # different and inside appium python client. Need to expose these and improve in order to make
157
+ # _find_by_key_attrs useful.
158
+ return self._find_by_id(application, criteria, tag, constraints)
159
+
160
+ # TODO: Not in use after conversion from Selenium2Library need to make more use of multiple auto selector strategy
161
+ def _find_by_key_attrs(self, application, criteria, tag, constraints):
162
+ key_attrs = self._key_attrs.get(None)
163
+ if tag is not None:
164
+ key_attrs = self._key_attrs.get(tag, key_attrs)
165
+
166
+ xpath_criteria = utils.escape_xpath_value(criteria)
167
+ xpath_tag = tag if tag is not None else '*'
168
+ xpath_constraints = ["@%s='%s'" % (name, constraints[name]) for name in constraints]
169
+ xpath_searchers = ["%s=%s" % (attr, xpath_criteria) for attr in key_attrs]
170
+ xpath_searchers.extend(
171
+ self._get_attrs_with_url(key_attrs, criteria, application))
172
+ xpath = "//%s[%s(%s)]" % (
173
+ xpath_tag,
174
+ ' and '.join(xpath_constraints) + ' and ' if len(xpath_constraints) > 0 else '',
175
+ ' or '.join(xpath_searchers))
176
+ return self._normalize_result(application.find_elements(by=AppiumBy.XPATH, value=xpath))
177
+
178
+ # Private
179
+ _key_attrs = {
180
+ None: ['@id', '@name'],
181
+ 'a': ['@id', '@name', '@href', 'normalize-space(descendant-or-self::text())'],
182
+ 'img': ['@id', '@name', '@src', '@alt'],
183
+ 'input': ['@id', '@name', '@value', '@src'],
184
+ 'button': ['@id', '@name', '@value', 'normalize-space(descendant-or-self::text())']
185
+ }
186
+
187
+ def _get_tag_and_constraints(self, tag):
188
+ if tag is None:
189
+ return None, {}
190
+
191
+ tag = tag.lower()
192
+ constraints = {}
193
+ if tag == 'link':
194
+ tag = 'a'
195
+ elif tag == 'image':
196
+ tag = 'img'
197
+ elif tag == 'list':
198
+ tag = 'select'
199
+ elif tag == 'radio button':
200
+ tag = 'input'
201
+ constraints['type'] = 'radio'
202
+ elif tag == 'checkbox':
203
+ tag = 'input'
204
+ constraints['type'] = 'checkbox'
205
+ elif tag == 'text field':
206
+ tag = 'input'
207
+ constraints['type'] = 'text'
208
+ elif tag == 'file upload':
209
+ tag = 'input'
210
+ constraints['type'] = 'file'
211
+ return tag, constraints
212
+
213
+ def _element_matches(self, element, tag, constraints):
214
+ if not element.tag_name.lower() == tag:
215
+ return False
216
+ for name in constraints:
217
+ if not element.get_attribute(name) == constraints[name]:
218
+ return False
219
+ return True
220
+
221
+ def _filter_elements(self, elements, tag, constraints):
222
+ elements = self._normalize_result(elements)
223
+ if tag is None:
224
+ return elements
225
+ return filter(
226
+ lambda element: self._element_matches(element, tag, constraints),
227
+ elements)
228
+
229
+ def _get_attrs_with_url(self, key_attrs, criteria, browser):
230
+ attrs = []
231
+ url = None
232
+ xpath_url = None
233
+ for attr in ['@src', '@href']:
234
+ if attr in key_attrs:
235
+ if url is None or xpath_url is None:
236
+ url = self._get_base_url(browser) + "/" + criteria
237
+ xpath_url = utils.escape_xpath_value(url)
238
+ attrs.append("%s=%s" % (attr, xpath_url))
239
+ return attrs
240
+
241
+ def _get_base_url(self, browser):
242
+ url = browser.get_current_url()
243
+ if '/' in url:
244
+ url = '/'.join(url.split('/')[:-1])
245
+ return url
246
+
247
+ def _parse_locator(self, locator):
248
+ prefix = None
249
+ criteria = locator
250
+ if not self._is_xpath(locator):
251
+ locator_parts = locator.partition('=')
252
+ if len(locator_parts[1]) > 0:
253
+ prefix = locator_parts[0].strip().lower()
254
+ criteria = locator_parts[2].strip()
255
+ return (prefix, criteria)
256
+
257
+ def _is_xpath(self, locator):
258
+ return locator and locator.startswith('/')
259
+
260
+ def _normalize_result(self, elements):
261
+ if not isinstance(elements, list):
262
+ logger.debug("WebDriver find returned %s" % elements)
263
+ return []
264
+ return elements
@@ -0,0 +1,50 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from robot.utils import abspath
5
+
6
+ from .applicationcache import ApplicationCache
7
+
8
+
9
+ def escape_xpath_value(value):
10
+ value = str(value)
11
+ if '"' in value and '\'' in value:
12
+ parts_wo_apos = value.split('\'')
13
+ return "concat('%s')" % "', \"'\", '".join(parts_wo_apos)
14
+ if '\'' in value:
15
+ return "\"%s\"" % value
16
+ return "'%s'" % value
17
+
18
+
19
+ def read_file(file_path):
20
+ with open(_absnorm(file_path), encoding='UTF-8', errors='strict', newline="") as f:
21
+ file_content = f.read().replace("\r\n", "\n")
22
+ return file_content
23
+
24
+
25
+ def _absnorm(path):
26
+ return abspath(_normalize_path(path))
27
+
28
+
29
+ def _normalize_path(path, case_normalize=False):
30
+ """Normalizes the given path.
31
+
32
+ - Collapses redundant separators and up-level references.
33
+ - Converts ``/`` to ``\\`` on Windows.
34
+ - Replaces initial ``~`` or ``~user`` by that user's home directory.
35
+ - Converts ``pathlib.Path`` instances to ``str``.
36
+ On Windows result would use ``\\`` instead of ``/`` and home directory
37
+ would be different.
38
+ """
39
+ if isinstance(path, Path):
40
+ path = str(path)
41
+ else:
42
+ path = path.replace("/", os.sep)
43
+ path = os.path.normpath(os.path.expanduser(path))
44
+ # os.path.normcase doesn't normalize on OSX which also, by default,
45
+ # has case-insensitive file system. Our robot.utils.normpath would
46
+ # do that, but it's not certain would that, or other things that the
47
+ # utility do, desirable.
48
+ if case_normalize:
49
+ path = os.path.normcase(path)
50
+ return path or "."
@@ -0,0 +1,48 @@
1
+ from robot.utils import ConnectionCache
2
+
3
+
4
+ class ApplicationCache(ConnectionCache):
5
+
6
+ def __init__(self):
7
+ ConnectionCache.__init__(self, no_current_msg='No current application')
8
+ self._closed = set()
9
+
10
+ @property
11
+ def applications(self):
12
+ return self._connections
13
+
14
+ def get_open_browsers(self):
15
+ open_applications = []
16
+ for application in self._connections:
17
+ if application not in self._closed:
18
+ open_applications.append(application)
19
+ return open_applications
20
+
21
+ def close(self, ignore_fail=False, quit_app=True):
22
+ if self.current:
23
+ application = self.current
24
+ try:
25
+ if quit_app:
26
+ application.quit()
27
+ else:
28
+ application.close()
29
+ except Exception as err:
30
+ if not ignore_fail:
31
+ raise err
32
+ self.current = self._no_current
33
+ self.current_index = None
34
+ self._closed.add(application)
35
+
36
+ def close_all(self, ignore_fail=True, quit_app=True):
37
+ for application in self._connections:
38
+ if application not in self._closed:
39
+ try:
40
+ if quit_app:
41
+ application.quit()
42
+ else:
43
+ application.close()
44
+ except Exception as err:
45
+ if not ignore_fail:
46
+ raise err
47
+ self.empty_cache()
48
+ return self.current
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ VERSION = '3.0-beta'
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotframework-appiumwindows
3
+ Version: 0.1.0
4
+ Summary: Robot Framework AppiumLibrary extension for Windows desktop automation using NovaWindows Driver instead of WinAppDriver.
5
+ Author-email: Huy Nguyen <nguyenvanhuy0612@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nguyenvanhuy0612/robotframework-appiumwindows
8
+ Project-URL: Repository, https://github.com/nguyenvanhuy0612/robotframework-appiumwindows
9
+ Project-URL: Issues, https://github.com/nguyenvanhuy0612/robotframework-appiumwindows/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Robot Framework
12
+ Classifier: Framework :: Robot Framework :: Library
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Classifier: Topic :: Software Development :: Quality Assurance
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: robotframework>=7.3.2
27
+ Requires-Dist: Appium-Python-Client>=5.1.1
28
+ Dynamic: license-file
29
+
30
+ # Robot Framework AppiumLibrary Compatible with NovaWindows Driver
31
+
32
+ ---
33
+
34
+ ## Overview
35
+ This library extends [AppiumLibrary](https://github.com/serhatbolsu/robotframework-appiumlibrary) to provide compatibility with the **NovaWindows Driver** for Appium 2.x.
36
+
37
+ It allows you to automate Windows desktop applications using **Robot Framework** with minimal setup.
38
+
39
+ ---
40
+
41
+ > **Note**
42
+ >
43
+ > - NovaWindows Driver currently uses a **PowerShell session** as its back-end.
44
+ > - No Developer Mode required
45
+ > - No extra dependencies required
46
+ > - A future update is planned to move to a **.NET-based backend** for:
47
+ > - Improved reliability
48
+ > - Better error handling
49
+ > - More feature support beyond PowerShell limitations
50
+ >
51
+ > Reference: [AutomateThePlanet/appium-novawindows-driver](https://github.com/AutomateThePlanet/appium-novawindows-driver)
52
+
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ ### 1. On the **Test Runner (local machine)**
58
+
59
+ Install the Robot Framework library:
60
+
61
+ ```bash
62
+ pip install robotframework-appiumwindows
63
+ ```
64
+
65
+ > This is the only requirement on the machine where you run Robot Framework tests.
66
+ > No need to install Node.js or Appium here.
67
+
68
+ ---
69
+
70
+ ### 2. On the **Target Machine (remote machine under test)**
71
+
72
+ This is where the Appium server and NovaWindows driver must be installed.
73
+
74
+ 1. Install **Node.js**
75
+ [Download Node.js](https://nodejs.org/en/download)
76
+
77
+ 2. Install **Appium** globally:
78
+ ```bash
79
+ npm install -g appium
80
+ ```
81
+
82
+ 3. Install **NovaWindows Driver**:
83
+ ```bash
84
+ appium driver install --source=npm appium-novawindows-driver
85
+ ```
86
+
87
+ 4. Start the Appium server:
88
+ ```bash
89
+ appium --relaxed-security
90
+ ```
91
+ (use `--relaxed-security` if you plan to execute PowerShell commands)
92
+
93
+ ---
94
+
95
+ ## Example Test
96
+
97
+ ```robot
98
+ *** Settings ***
99
+ Library AppiumLibrary
100
+
101
+ Test Setup Open Root Session
102
+ Test Teardown Appium Close All Applications
103
+
104
+
105
+ *** Test Cases ***
106
+ Type To Notepad
107
+ [Documentation] Launch Notepad, type text, and close without saving
108
+ Appium Execute Powershell Command Start-Process "notepad"
109
+ Appium Input class=Notepad This is example{enter 3}Close without save
110
+ Appium Click //Window[@ClassName='Notepad']//Button[@Name='Close']
111
+ Appium Click name=Don't Save
112
+
113
+
114
+ *** Keywords ***
115
+ Open Root Session
116
+ ${parameters}= Create Dictionary
117
+ ... remote_url=http://<TARGET_MACHINE_IP>:4723
118
+ ... platformName=Windows
119
+ ... appium:app=Root
120
+ ... appium:automationName=NovaWindows
121
+ ... appium:newCommandTimeout=30
122
+ Open Application &{parameters}
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Architecture
128
+
129
+ ```text
130
+ +--------------------------+ +----------------------------+
131
+ | Test Runner (Local PC) | | Target Machine (Windows) |
132
+ |--------------------------| |----------------------------|
133
+ | - Robot Framework | | - Node.js |
134
+ | - robotframework- | ---> | - Appium 2.x |
135
+ | appium-windows (pip) | | - NovaWindows Driver |
136
+ +--------------------------+ +----------------------------+
137
+ ```
138
+
139
+ ---
140
+
141
+ ## References
142
+
143
+ - [Appium](https://appium.io/)
144
+ - [Robot Framework](https://robotframework.org/)
145
+ - [AppiumLibrary](https://github.com/serhatbolsu/robotframework-appiumlibrary)
146
+ - [NovaWindows Driver](https://github.com/AutomateThePlanet/appium-novawindows-driver)
147
+
148
+ ---
@@ -0,0 +1,23 @@
1
+ AppiumLibrary/__init__.py,sha256=_8Mcsywy1P1ebzUl0Cn49RCLSHzG4zRrFQTNAL1r_2o,5901
2
+ AppiumLibrary/appium_path.py,sha256=0LZFLn07dsxOY0FIJ4REEfyRbNBxtgNzX-rEfovEh_4,260
3
+ AppiumLibrary/version.py,sha256=hrqXUu4slpU-usm7Rv3LJhMR6paooe0eSmHZXr8iYHY,47
4
+ AppiumLibrary/keywords/__init__.py,sha256=vRZLNwHfUSaBPBY3dmDUejCihFL0N2ODYkrubhBwcjA,767
5
+ AppiumLibrary/keywords/_applicationmanagement.py,sha256=qJYKpNjHEcNvXlWVfKIh-xCUkWJGyDTWigl2-nAJSMk,19786
6
+ AppiumLibrary/keywords/_element.py,sha256=MsY69h0ZiRc8Tl6K4KU56k8fgWHv4WeHwIGm_h68TTE,53655
7
+ AppiumLibrary/keywords/_logging.py,sha256=r9P56w85BFQIZw3pMQtFliu7uD4sqoCITDXDhB_LURM,1927
8
+ AppiumLibrary/keywords/_powershell.py,sha256=j_-LrLIOkx6sdKj-P-bUerfcdpHIC_fIG4VZT4G2Ovo,23611
9
+ AppiumLibrary/keywords/_runonfailure.py,sha256=t6kJrVJdCo9cHbuUUs1Qoy63Z7AKTrDBepOIxvsSNDQ,2921
10
+ AppiumLibrary/keywords/_screenrecord.py,sha256=e8EXHTwiujeQVMrDhmYq6QIQk3MwSuuFpu8SWHZ0r7Q,6927
11
+ AppiumLibrary/keywords/_screenshot.py,sha256=L_o1ncYsJ_ghJEVrNVlilXCxvkR9KyR7D-twZIAmEAQ,3998
12
+ AppiumLibrary/keywords/_waiting.py,sha256=vDp7duuVuRsmtpf4DYgyvP363KpG9Ldffr2TD3GqTcI,6446
13
+ AppiumLibrary/keywords/_windows.py,sha256=W7M5ba1IZ7YdAn3CPP58fq-CrBC_Ck-GaKXVh7dJjFE,9666
14
+ AppiumLibrary/keywords/keywordgroup.py,sha256=SvgJNazRMZoyH4pBmGY_05l1FjVDaoVfwX6I3YN_fU0,2449
15
+ AppiumLibrary/locators/__init__.py,sha256=-t6gBYeVN8LS1q_agwCNikDaH54v_JXPfRtE5slY9dg,109
16
+ AppiumLibrary/locators/elementfinder.py,sha256=EdKVUJIHZCxrQkAu-0xp3yhCwA5qd5y55SnP58-dBP0,11241
17
+ AppiumLibrary/utils/__init__.py,sha256=FlB2DCknRHFvtj4ZZbq3B0sSmZ9RTnvqucHNC9pReN4,1622
18
+ AppiumLibrary/utils/applicationcache.py,sha256=AeeZIzRLxqcOLw9lIAir2_CypGAEwglCzzvMep4IQrg,1571
19
+ robotframework_appiumwindows-0.1.0.dist-info/licenses/LICENSE,sha256=yVLQqHcOliLXZJC6M0K2u0WJL9yIW1u7d5bsM_qW1Es,1090
20
+ robotframework_appiumwindows-0.1.0.dist-info/METADATA,sha256=HXX0laXT4t3XFysxHQL8Pr9G_bD6cs930X2-5Aux_iE,4902
21
+ robotframework_appiumwindows-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ robotframework_appiumwindows-0.1.0.dist-info/top_level.txt,sha256=2o2iQDagXnzsewODgcttx95vTzvaYaZB7q6KJB8sf-I,14
23
+ robotframework_appiumwindows-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nguyen Van Huy
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
+ AppiumLibrary