matlab-proxy 0.14.0__py3-none-any.whl → 0.15.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.

Potentially problematic release.


This version of matlab-proxy might be problematic. Click here for more details.

@@ -0,0 +1,115 @@
1
+ # Copyright 2023-2024 The MathWorks, Inc.
2
+
3
+ import pytest
4
+ from tests.integration.utils import integration_tests_utils as utils
5
+ import requests
6
+ from tests.utils.logging_util import create_integ_test_logger
7
+ import os
8
+ from urllib.parse import urlparse, parse_qs
9
+
10
+ _logger = create_integ_test_logger(__name__)
11
+
12
+
13
+ @pytest.fixture(scope="module", name="module_monkeypatch")
14
+ def monkeypatch_module_scope_fixture():
15
+ """
16
+ Pytest fixture for creating a monkeypatch object in 'module' scope.
17
+ The default monkeypatch fixture returns monkeypatch object in
18
+ 'function' scope but a 'module' scope object is needed with matlab-proxy
19
+ 'module' scope fixture.
20
+
21
+ Yields:
22
+ class object: Object of class MonkeyPatch
23
+ """
24
+ with pytest.MonkeyPatch.context() as mp:
25
+ yield mp
26
+
27
+
28
+ @pytest.fixture
29
+ def parse_matlab_proxy_url():
30
+ # Get the base URL from a utility function or environment variable
31
+ mwi_app_port = os.environ["MWI_APP_PORT"]
32
+
33
+ # Construct the parameters dictionary
34
+ parsed_url = urlparse(utils.get_connection_string(mwi_app_port))
35
+
36
+ headers = {
37
+ "mwi_auth_token": (
38
+ parse_qs(parsed_url.query)["mwi_auth_token"][0]
39
+ if "mwi_auth_token" in parse_qs(parsed_url.query)
40
+ else ""
41
+ )
42
+ }
43
+ connection_scheme = parsed_url.scheme
44
+
45
+ # Return the base URL and parameters as a tuple
46
+ return parsed_url, headers, connection_scheme
47
+
48
+
49
+ @pytest.fixture(scope="module", autouse=True)
50
+ def start_matlab_proxy_fixture(module_monkeypatch):
51
+ """Starts the matlab proxy process"""
52
+ utils.perform_basic_checks()
53
+
54
+ # Start matlab-proxy-app for testing
55
+
56
+ mwi_app_port = utils.get_random_free_port()
57
+ mwi_base_url = "/matlab-test"
58
+
59
+ input_env = {
60
+ "MWI_APP_PORT": mwi_app_port,
61
+ "MWI_BASE_URL": mwi_base_url,
62
+ }
63
+
64
+ import shutil
65
+ import matlab_proxy
66
+
67
+ matlab_config_file = str(
68
+ utils.get_matlab_config_file()
69
+ ) # ~/.matlab/MWI/hosts/hostname/proxy_app_config.json
70
+
71
+ _logger.info(f"matlab_config_file {matlab_config_file}")
72
+
73
+ dotmatlab_dir_path = os.path.dirname(os.path.dirname(matlab_config_file))
74
+
75
+ # Create a temporary location in .matlab directory
76
+ temp_dir_path = os.path.join(dotmatlab_dir_path, "temp_dir")
77
+ os.mkdir(temp_dir_path) # delete this folder after the test execution
78
+ shutil.move(matlab_config_file, temp_dir_path)
79
+
80
+ loop = matlab_proxy.util.get_event_loop()
81
+
82
+ # Run matlab-proxy in the background in an event loop
83
+ proc = loop.run_until_complete(utils.start_matlab_proxy_app(input_env=input_env))
84
+ _logger.info("Started MATLAB Proxy process")
85
+
86
+ # Wait for mwi_server.info file to be ready
87
+ utils.wait_server_info_ready(mwi_app_port)
88
+
89
+ # Get the scheme on which MATLAB Proxy connection string
90
+ matlab_proxy_url = utils.get_connection_string(mwi_app_port)
91
+
92
+ utils.poll_web_service(
93
+ matlab_proxy_url,
94
+ step=5,
95
+ timeout=120,
96
+ ignore_exceptions=(
97
+ requests.exceptions.ConnectionError,
98
+ requests.exceptions.SSLError,
99
+ ),
100
+ )
101
+
102
+ for key, value in input_env.items():
103
+ module_monkeypatch.setenv(key, value)
104
+
105
+ yield
106
+ from pathlib import Path
107
+
108
+ shutil.move(
109
+ str(temp_dir_path / Path("proxy_app_config.json")),
110
+ str(os.path.dirname(matlab_config_file)),
111
+ )
112
+ shutil.rmtree(temp_dir_path)
113
+
114
+ proc.terminate()
115
+ loop.run_until_complete(proc.wait())
@@ -0,0 +1,46 @@
1
+ # Copyright 2023-2024 The MathWorks, Inc.
2
+
3
+ import os
4
+ import json
5
+ import requests
6
+ from requests.adapters import HTTPAdapter, Retry
7
+ from tests.integration.utils import integration_tests_utils as utils
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ def test_matlab_down(parse_matlab_proxy_url):
12
+ """Test that matlab is down and no license is picked up"""
13
+
14
+ parsed_url, headers, connection_scheme = parse_matlab_proxy_url
15
+ http_endpoint = "/get_status"
16
+ uri = (
17
+ connection_scheme + "://" + parsed_url.netloc + parsed_url.path + http_endpoint
18
+ )
19
+
20
+ json_response = None
21
+
22
+ with requests.Session() as s:
23
+ retries = Retry(total=10, backoff_factor=0.1)
24
+ s.mount(f"{connection_scheme}://", HTTPAdapter(max_retries=retries))
25
+ response = s.get(uri, headers=headers, verify=False)
26
+ json_response = json.loads(response.text)
27
+
28
+ matlab_status = json_response["matlab"]["status"]
29
+ assert matlab_status == "down"
30
+
31
+
32
+ def test_matlab_proxy_app_installed():
33
+ import shutil
34
+
35
+ """Test that the executable matlab_proxy_app is located on PATH and executable"""
36
+
37
+ which_matlabproxyapp = shutil.which("matlab-proxy-app")
38
+ assert (
39
+ which_matlabproxyapp is not None
40
+ ), "matlab-proxy-app does not exist in system path"
41
+ assert (
42
+ os.access(which_matlabproxyapp, os.R_OK) == True
43
+ ), """matlab-proxy-app does not have the read permissions"""
44
+ assert (
45
+ os.access(which_matlabproxyapp, os.X_OK) == True
46
+ ), """matlab-proxy-app does not have the execute permissions"""
@@ -0,0 +1 @@
1
+ # Copyright 2023-2024 The MathWorks, Inc.
@@ -0,0 +1,352 @@
1
+ # Copyright 2023-2024 The MathWorks, Inc.
2
+ # Utility functions for integration testing of matlab-proxy
3
+
4
+ import asyncio
5
+ import os
6
+ import socket
7
+ import time
8
+ import urllib3
9
+ import requests
10
+ import json
11
+ from tests.utils.logging_util import create_integ_test_logger
12
+
13
+ _logger = create_integ_test_logger(__name__)
14
+
15
+
16
+ def perform_basic_checks():
17
+ """
18
+ Perform basic checks for the prerequisites for starting
19
+ matlab-proxy
20
+ """
21
+ import matlab_proxy.settings
22
+
23
+ _logger.info("Performing basic checks for matlab-proxy")
24
+
25
+ # Validate MATLAB before testing
26
+ _, matlab_path = matlab_proxy.settings.get_matlab_executable_and_root_path()
27
+
28
+ # Check if MATLAB is in the system path
29
+ assert matlab_path is not None, "MATLAB is not in system path"
30
+
31
+ # Check if MATLAB verison is >= R2020b
32
+ assert (
33
+ matlab_proxy.settings.get_matlab_version(matlab_path) >= "R2020b"
34
+ ), "MATLAB version should be R2020b or later"
35
+ _logger.debug("Exiting perform_basic_checks")
36
+
37
+
38
+ def matlab_proxy_cmd_for_testing():
39
+ """
40
+ Get command for starting matlab-proxy process
41
+
42
+ Returns:
43
+ list(string): Command for starting matlab-proxy process
44
+ """
45
+
46
+ import matlab_proxy
47
+
48
+ matlab_cmd = [matlab_proxy.get_executable_name()]
49
+
50
+ return matlab_cmd
51
+
52
+
53
+ async def start_matlab_proxy_app(out=asyncio.subprocess.PIPE, input_env={}):
54
+ """
55
+ Starts matlab-proxy as a subprocess. The subprocess runs forever unless
56
+ there is any error
57
+
58
+ Args:
59
+ input_env (dict, optional): Environment variables to be
60
+ initialized for the subprocess. Defaults to {}.
61
+
62
+ Returns:
63
+ Process: subprocess object
64
+ """
65
+ from matlab_proxy.util import system
66
+
67
+ _logger.info("Starting MATLAB Proxy app")
68
+ cmd = matlab_proxy_cmd_for_testing()
69
+ matlab_proxy_env = os.environ.copy()
70
+ matlab_proxy_env.update(input_env)
71
+
72
+ proc = await asyncio.create_subprocess_exec(
73
+ *cmd,
74
+ env=matlab_proxy_env,
75
+ stdout=out,
76
+ stderr=out,
77
+ )
78
+ _logger.debug("MATLAB Proxy App started")
79
+ return proc
80
+
81
+
82
+ def send_http_request(
83
+ connection_scheme,
84
+ mwi_app_port,
85
+ mwi_base_url="",
86
+ http_endpoint="",
87
+ method="GET",
88
+ headers={},
89
+ ):
90
+ """Send HTTP request to matlab-proxy server.
91
+ Returns HTTP response JSON"""
92
+
93
+ uri = (
94
+ f"{connection_scheme}://localhost:{mwi_app_port}{mwi_base_url}/{http_endpoint}"
95
+ )
96
+
97
+ with urllib3.PoolManager(
98
+ retries=urllib3.Retry(backoff_factor=0.1, backoff_max=300)
99
+ ) as http:
100
+ res = http.request(method, uri, fields=headers)
101
+ return json.loads(res.data.decode("utf-8"))
102
+
103
+
104
+ def wait_matlab_proxy_ready(matlab_proxy_url):
105
+ """
106
+ Wait for matlab-proxy to be up and running
107
+
108
+ Args:
109
+ matlab_proxy_url (string): URL to access matlab-proxy
110
+ """
111
+
112
+ from matlab_proxy.util import system
113
+ import matlab_proxy.settings as settings
114
+
115
+ _logger.info("Wait for MATLAB Proxy to start")
116
+ # Wait until the matlab config file is created
117
+ MAX_TIMEOUT = settings.get_process_startup_timeout()
118
+ start_time = time.time()
119
+
120
+ while not os.path.exists(str(get_matlab_config_file())) and (
121
+ time.time() - start_time < MAX_TIMEOUT
122
+ ):
123
+ time.sleep(1)
124
+
125
+ try:
126
+ if not os.path.exists(str(get_matlab_config_file())):
127
+ raise FileNotFoundError("Config file is not present on the path")
128
+
129
+ except FileNotFoundError as e:
130
+ _logger.exception("Config file does not exist")
131
+ _logger.exception(e)
132
+
133
+
134
+ def get_random_free_port() -> str:
135
+ """
136
+ Get a random free port
137
+
138
+ Returns:
139
+ string: A random free port
140
+ """
141
+
142
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
143
+ s.bind(("", 0))
144
+ port = str(s.getsockname()[1])
145
+ s.close()
146
+ return port
147
+
148
+
149
+ def wait_server_info_ready(port_number):
150
+ """
151
+ Waits for server file to be available on the disk
152
+ """
153
+
154
+ import matlab_proxy.settings as settings
155
+
156
+ # timeout_value is in seconds
157
+ timeout_value = 60
158
+ start_time = time.time()
159
+ config_folder = settings.get_mwi_config_folder()
160
+ _path = config_folder / "ports" / port_number / "mwi_server.info"
161
+
162
+ while time.time() - start_time < timeout_value:
163
+ if _path.exists():
164
+ return True
165
+
166
+ time.sleep(1)
167
+
168
+ raise FileNotFoundError("mwi_server.info file does not exist")
169
+
170
+
171
+ def license_matlab_proxy(matlab_proxy_url):
172
+ """
173
+ Use Playwright UI automation to license matlab-proxy.
174
+ Uses TEST_USERNAME and TEST_PASSWORD from environment variables.
175
+
176
+ Args:
177
+ matlab_proxy_url (string): URL to access matlab-proxy
178
+ """
179
+ from playwright.sync_api import sync_playwright, expect
180
+
181
+ _logger.info("Licensing MATLAB using matlab-proxy")
182
+ # These are MathWorks Account credentials to license MATLAB
183
+ # Throws 'KeyError' if the following environment variables are not set
184
+ TEST_USERNAME = os.environ["TEST_USERNAME"]
185
+ TEST_PASSWORD = os.environ["TEST_PASSWORD"]
186
+
187
+ with sync_playwright() as playwright:
188
+ browser = playwright.chromium.launch(headless=True)
189
+ context = browser.new_context(ignore_https_errors=True)
190
+
191
+ page = context.new_page()
192
+
193
+ page.goto(matlab_proxy_url)
194
+
195
+ # Find the MHLM licensing windows in matlab-proxy
196
+ mhlm_div = page.locator("#MHLM")
197
+ expect(
198
+ mhlm_div,
199
+ "Wait for MHLM licensing window to appear. This might fail if the MATLAB is already licensed",
200
+ ).to_be_visible(timeout=60000)
201
+
202
+ # The login iframe is present within the MHLM Div
203
+ login_iframe = mhlm_div.frame_locator("#loginframe")
204
+
205
+ # Fills in the username textbox
206
+ email_text_box = login_iframe.locator("#userId")
207
+ expect(
208
+ email_text_box,
209
+ "Wait for email ID textbox to appear",
210
+ ).to_be_visible(timeout=20000)
211
+ email_text_box.fill(TEST_USERNAME)
212
+ email_text_box.press("Enter")
213
+
214
+ # Fills in the password textbox
215
+ password_text_box = login_iframe.locator("#password")
216
+ expect(password_text_box, "Wait for password textbox to appear").to_be_visible(
217
+ timeout=20000
218
+ )
219
+ password_text_box.fill(TEST_PASSWORD)
220
+ password_text_box.press("Enter")
221
+
222
+ # Verifies if licensing is successful by checking the status information
223
+ status_info = page.get_by_text("Status Information")
224
+ expect(
225
+ status_info,
226
+ "Verify if Licensing is successful. This might fail if incorrect credentials are provided",
227
+ ).to_be_visible(timeout=60000)
228
+ _logger.debug("Succeeded in licensing MATLAB using matlab-proxy")
229
+ browser.close()
230
+
231
+
232
+ def unlicense_matlab_proxy(matlab_proxy_url):
233
+ """
234
+ Unlicense matlab-proxy that is licensed using online licensing
235
+
236
+ Args:
237
+ matlab_proxy_url (string): URL to access matlab-proxy
238
+ """
239
+ import warnings
240
+
241
+ _logger.info("Unlicensing matlab-proxy")
242
+ max_retries = 3 # Max retries for unlicensing matlab-proxy
243
+ retries = 0
244
+
245
+ while retries < max_retries:
246
+ error = None
247
+ try:
248
+ resp = requests.delete(
249
+ matlab_proxy_url + "/set_licensing_info", headers={}, verify=False
250
+ )
251
+
252
+ if resp.status_code == requests.codes.OK:
253
+ data = resp.json()
254
+ assert data["licensing"] == None, "matlab-proxy licensing is not unset"
255
+ assert (
256
+ data["matlab"]["status"] == "down"
257
+ ), "matlab-proxy is not in 'stopped' state"
258
+
259
+ # Throw warning if matlab-proxy is unlicensed but with some error
260
+ if data["error"] != None:
261
+ warnings.warn(
262
+ f"matlab-proxy is unlicensed but with error: {data['error']}",
263
+ UserWarning,
264
+ )
265
+ _logger.debug("Succeeded in unlicensing matlab-proxy")
266
+ break
267
+ else:
268
+ resp.raise_for_status()
269
+ except Exception as e:
270
+ error = e
271
+ finally:
272
+ retries += 1
273
+
274
+ # If the above code threw error even after maximum retries, then raise error
275
+ if error:
276
+ raise error
277
+
278
+
279
+ def get_connection_string(port_number):
280
+ """Returns the scheme on which matlab proxy starts (http or https)
281
+
282
+ Args:
283
+ port_number (string): Port on which MATLAB proxy starts
284
+
285
+ Returns:
286
+ scheme: String representing the scheme
287
+ """
288
+ import matlab_proxy.settings as settings
289
+
290
+ config_folder = settings.get_mwi_config_folder()
291
+ _path = config_folder / "ports" / port_number / "mwi_server.info"
292
+ conn_string = None
293
+
294
+ try:
295
+ with open(_path, "r") as _file:
296
+ conn_string = _file.readline().rstrip()
297
+
298
+ except FileNotFoundError:
299
+ _logger.exception(f"{_path} does not exist")
300
+
301
+ return conn_string
302
+
303
+
304
+ def poll_web_service(url, step=1, timeout=60, ignore_exceptions=None):
305
+ """Poll a web service for a 200 response
306
+
307
+ Args:
308
+ url (string): URL of the web service
309
+ step (int, optional): Poll Interval. Defaults to 1 second.
310
+ timeout (int, optional): Polling timout. Defaults to 60 seconds.
311
+ ignore_exceptions (tuple, optional): The exceptions that need to be ignored
312
+ within the polling timout. Defaults to None.
313
+
314
+ Raises:
315
+ TimeoutError: Error if polling timeout is exceeded
316
+
317
+ Returns:
318
+ dict: response dictionary object
319
+ """
320
+ start_time = time.time()
321
+ end_time = start_time + timeout
322
+
323
+ import matlab_proxy.settings as settings
324
+
325
+ config_folder = settings.get_mwi_config_folder()
326
+
327
+ while time.time() < end_time:
328
+ try:
329
+ response = requests.get(url, verify=False)
330
+ if response.status_code == 200:
331
+ return response
332
+
333
+ except Exception as e:
334
+ if ignore_exceptions and isinstance(e, ignore_exceptions):
335
+ continue # Ignore specified exceptions
336
+ time.sleep(step)
337
+
338
+ raise TimeoutError(
339
+ f"{url} did not return a 200 response within the timeout period {timeout} seconds."
340
+ )
341
+
342
+
343
+ def get_matlab_config_file():
344
+ """
345
+ Gets the path to MATLAB config file generated by matlab-proxy
346
+
347
+ Returns:
348
+ string: MATLAB config file path
349
+ """
350
+ from matlab_proxy import settings
351
+
352
+ return settings.get()["matlab_config_file"]
@@ -0,0 +1,152 @@
1
+ # Copyright 2024 The MathWorks, Inc.
2
+ import os
3
+ import logging
4
+ from playwright.sync_api import Page, Error, sync_playwright, expect
5
+ from tests.integration.utils import integration_tests_utils as utils
6
+ from tests.utils.logging_util import create_integ_test_logger
7
+
8
+ # Configure logging
9
+ _logger = create_integ_test_logger(__name__)
10
+
11
+ TIMEOUTS = {
12
+ # Time in milliseconds
13
+ "MHLM_VISIBLE": 60 * 1000,
14
+ "TEXTBOX_VISIBLE": 5 * 1000,
15
+ "MATLAB_STARTS": 3 * 60 * 1000,
16
+ }
17
+
18
+
19
+ POLL_INTERVAL = 1000
20
+
21
+
22
+ def _get_matlab_proxy_url():
23
+ # import integration_tests_utils as utils
24
+ import matlab_proxy.util
25
+
26
+ mwi_app_port = utils.get_random_free_port()
27
+ mwi_base_url = "/matlab-test"
28
+
29
+ input_env = {
30
+ "MWI_APP_PORT": mwi_app_port,
31
+ "MWI_BASE_URL": mwi_base_url,
32
+ }
33
+
34
+ loop = matlab_proxy.util.get_event_loop()
35
+ # Run matlab-proxy in the background in an event loop
36
+ proc = loop.run_until_complete(utils.start_matlab_proxy_app(input_env=input_env))
37
+
38
+ utils.wait_server_info_ready(mwi_app_port)
39
+ matlab_proxy_url = utils.get_connection_string(mwi_app_port)
40
+ return matlab_proxy_url, proc, loop
41
+
42
+
43
+ def license_matlab_proxy():
44
+ import requests
45
+
46
+ matlab_proxy_url, proc, loop = _get_matlab_proxy_url()
47
+
48
+ utils.poll_web_service(
49
+ matlab_proxy_url,
50
+ step=5,
51
+ timeout=120,
52
+ ignore_exceptions=(
53
+ requests.exceptions.ConnectionError,
54
+ requests.exceptions.SSLError,
55
+ ),
56
+ )
57
+
58
+ licensing_with_online_licensing(matlab_proxy_url)
59
+ utils.wait_matlab_proxy_ready(matlab_proxy_url)
60
+ proc.terminate()
61
+ loop.run_until_complete(proc.wait())
62
+
63
+
64
+ def licensing_with_online_licensing(matlab_proxy_url):
65
+ """
66
+ Use Playwright UI automation to license matlab-proxy.
67
+ Uses TEST_USERNAME and TEST_PASSWORD from environment variables.
68
+
69
+ Args:
70
+ matlab_proxy_url (string): URL to access matlab-proxy
71
+ """
72
+ from playwright.sync_api import sync_playwright, expect
73
+
74
+ # These are MathWorks Account credentials to license MATLAB
75
+ # Throws 'KeyError' if the following environment variables are not set
76
+ TEST_USERNAME = os.environ["TEST_USERNAME"]
77
+ TEST_PASSWORD = os.environ["TEST_PASSWORD"]
78
+
79
+ playwright, browser, page = _launch_browser()
80
+ page.goto(matlab_proxy_url)
81
+
82
+ # Find the MHLM licensing window in matlab-proxy
83
+ login_iframe = _wait_for_login_iframe(page)
84
+
85
+ # Fills in the username textbox
86
+ email_text_box = _fill_in_username(TEST_USERNAME, login_iframe)
87
+ email_text_box.press("Enter")
88
+
89
+ # Fills in the password textbox
90
+ password_text_box = _fill_in_password(TEST_PASSWORD, login_iframe)
91
+ password_text_box.press("Enter")
92
+
93
+ # Verifies if licensing is successful by checking the status information
94
+ _verify_licensing(page)
95
+ _close_resources(playwright, browser)
96
+
97
+
98
+ def _verify_licensing(page):
99
+ status_info = page.get_by_text("Status Information")
100
+ expect(
101
+ status_info,
102
+ "Verify if Licensing is successful. This might fail if incorrect credentials are provided",
103
+ ).to_be_visible(timeout=TIMEOUTS["MHLM_VISIBLE"])
104
+
105
+
106
+ def _wait_for_login_iframe(matlab_proxy_page):
107
+ """Waits for the MHLM/Online Licensing form to appear."""
108
+ mhlm_div = matlab_proxy_page.locator("#MHLM")
109
+ expect(
110
+ mhlm_div,
111
+ "Wait for MHLM licensing window to appear. This might fail if the MATLAB is already licensed",
112
+ ).to_be_visible(timeout=TIMEOUTS["MHLM_VISIBLE"])
113
+
114
+ # The login iframe is present within the MHLM Div
115
+ login_iframe = mhlm_div.frame_locator("#loginframe")
116
+ return login_iframe
117
+
118
+
119
+ def _launch_browser(headless: bool = True) -> tuple:
120
+ """Launches the browser and returns the browser and page objects."""
121
+ playwright = sync_playwright().start()
122
+ browser = playwright.chromium.launch(headless=headless)
123
+ context = browser.new_context(ignore_https_errors=True)
124
+ page = context.new_page()
125
+ return playwright, browser, page
126
+
127
+
128
+ def _close_resources(playwright, browser):
129
+ """Closes the browser and playwright resources properly."""
130
+ browser.close()
131
+ playwright.stop()
132
+
133
+
134
+ def _fill_in_username(username, login_iframe):
135
+ """Inputs the provided username string into the MHLM login form."""
136
+ email_text_box = login_iframe.locator("#userId")
137
+ expect(
138
+ email_text_box,
139
+ "Wait for email ID textbox to appear",
140
+ ).to_be_visible(timeout=TIMEOUTS["TEXTBOX_VISIBLE"])
141
+ email_text_box.fill(username)
142
+ return email_text_box
143
+
144
+
145
+ def _fill_in_password(password, login_iframe):
146
+ """Inputs the provided password string into the MHLM login form."""
147
+ password_text_box = login_iframe.locator("#password")
148
+ expect(password_text_box, "Wait for password textbox to appear").to_be_visible(
149
+ timeout=TIMEOUTS["TEXTBOX_VISIBLE"]
150
+ )
151
+ password_text_box.fill(password)
152
+ return password_text_box
@@ -0,0 +1 @@
1
+ # Copyright 2023-2024 The MathWorks, Inc.