1+ # pylint: disable=logging-fstring-interpolation
2+ # pylint:disable=no-value-for-parameter
3+ # pylint:disable=protected-access
4+ # pylint:disable=redefined-outer-name
5+ # pylint:disable=too-many-arguments
6+ # pylint:disable=too-many-statements
7+ # pylint:disable=unused-argument
8+ # pylint:disable=unused-variable
9+
10+ import json
111import logging
212import re
3- from collections .abc import Callable
13+ from collections .abc import Callable , Iterator
414from typing import Any , Final
515
6- from playwright .sync_api import Page
16+ import pytest
17+ from playwright .sync_api import APIRequestContext , Page
718from pydantic import AnyUrl
819from pytest_simcore .helpers .logging_tools import log_context
920from pytest_simcore .helpers .playwright import (
1425)
1526
1627_WAITING_FOR_SERVICE_TO_START : Final [int ] = 5 * MINUTE
28+ _WAITING_FOR_SERVICE_TO_APPEAR : Final [int ] = 2 * MINUTE
1729_DEFAULT_RESPONSE_TO_WAIT_FOR : Final [re .Pattern ] = re .compile (
1830 r"/flask/list_function_job_collections_for_functionid"
1931)
2032
33+ _STUDY_FUNCTION_NAME : Final [str ] = "playwright_test_study_for_rsm"
34+ _FUNCTION_NAME : Final [str ] = "playwright_test_function"
35+
36+
37+ @pytest .fixture
38+ def create_function_from_project (
39+ api_request_context : APIRequestContext ,
40+ is_product_billable : bool ,
41+ product_url : AnyUrl ,
42+ ) -> Iterator [Callable [[Page , str ], dict [str , Any ]]]:
43+ created_function_uuids : list [str ] = []
44+
45+ def _create_function_from_project (
46+ page : Page ,
47+ project_uuid : str ,
48+ ) -> dict [str , Any ]:
49+ with log_context (
50+ logging .INFO ,
51+ f"Convert { project_uuid = } / { _STUDY_FUNCTION_NAME } to a function" ,
52+ ) as ctx :
53+ with page .expect_response (re .compile (rf"/projects/{ project_uuid } " )):
54+ page .get_by_test_id (f"studyBrowserListItem_{ project_uuid } " ).click ()
55+ page .wait_for_timeout (2000 )
56+ page .get_by_text ("create function" ).first .click ()
57+ page .wait_for_timeout (2000 )
58+
59+ with page .expect_response (
60+ lambda response : re .compile (r"/functions" ).search (response .url )
61+ is not None
62+ and response .request .method == "POST"
63+ ) as create_function_response :
64+ page .get_by_test_id ("create_function_page_btn" ).click ()
65+ assert (
66+ create_function_response .value .ok
67+ ), f"Failed to create function: { create_function_response .value .status } "
68+ function_data = create_function_response .value .json ()
69+
70+ ctx .logger .info (
71+ "Created function: %s" , f"{ json .dumps (function_data ['data' ], indent = 2 )} "
72+ )
73+
74+ page .keyboard .press ("Escape" )
75+ created_function_uuids .append (function_data ["data" ]["uuid" ])
76+ return function_data ["data" ]
77+
78+ yield _create_function_from_project
79+
80+ # cleanup the functions
81+ for function_uuid in created_function_uuids :
82+ with log_context (
83+ logging .INFO ,
84+ f"Delete function with { function_uuid = } in { product_url = } as { is_product_billable = } " ,
85+ ):
86+ response = api_request_context .delete (
87+ f"{ product_url } v0/functions/{ function_uuid } "
88+ )
89+ assert (
90+ response .status == 204
91+ ), f"Unexpected error while deleting project: '{ response .json ()} '"
92+
2193
2294def test_response_surface_modeling (
2395 page : Page ,
@@ -29,15 +101,88 @@ def test_response_surface_modeling(
29101 service_version : str | None ,
30102 product_url : AnyUrl ,
31103 is_service_legacy : bool ,
104+ create_function_from_project : Callable [[Page , str ], dict [str , Any ]],
32105):
106+ # 1. create the initial study with jsonifier
107+ with log_context (logging .INFO , "Create new study for function" ):
108+ jsonifier_project_data = create_project_from_service_dashboard (
109+ ServiceType .COMPUTATIONAL , "jsonifier" , None , service_version
110+ )
111+ assert (
112+ "workbench" in jsonifier_project_data
113+ ), "Expected workbench to be in project data!"
114+ assert isinstance (
115+ jsonifier_project_data ["workbench" ], dict
116+ ), "Expected workbench to be a dict!"
117+ node_ids : list [str ] = list (jsonifier_project_data ["workbench" ])
118+ assert len (node_ids ) == 1 , "Expected 1 node in the workbench!"
119+
120+ # select the jsonifier, it's the second one as the study has the same name
121+ page .get_by_test_id ("nodeTreeItem" ).filter (has_text = "jsonifier" ).all ()[
122+ 1
123+ ].click ()
124+
125+ # create the probe
126+ with page .expect_response (
127+ lambda response : re .compile (
128+ rf"/projects/{ jsonifier_project_data ['uuid' ]} "
129+ ).search (response .url )
130+ is not None
131+ and response .request .method == "PATCH"
132+ ):
133+ page .get_by_test_id ("connect_probe_btn_number_3" ).click ()
134+
135+ # # create the parameter
136+ page .get_by_test_id ("connect_input_btn_number_1" ).click ()
137+ with page .expect_response (
138+ lambda response : re .compile (
139+ rf"/projects/{ jsonifier_project_data ['uuid' ]} "
140+ ).search (response .url )
141+ is not None
142+ and response .request .method == "PATCH"
143+ ):
144+ page .get_by_text ("new parameter" ).click ()
145+
146+ # rename the project to identify it
147+ page .get_by_test_id ("studyTitleRenamer" ).click ()
148+ with page .expect_response (
149+ lambda response : re .compile (
150+ rf"/projects/{ jsonifier_project_data ['uuid' ]} "
151+ ).search (response .url )
152+ is not None
153+ and response .request .method == "PATCH"
154+ ):
155+ page .get_by_test_id ("studyTitleRenamer" ).locator ("input" ).fill (
156+ _STUDY_FUNCTION_NAME
157+ )
158+
159+ # 2. go back to dashboard
33160 with (
34- log_context (
35- logging .INFO ,
36- f"Waiting for { service_key } to be responsive (waiting for { _DEFAULT_RESPONSE_TO_WAIT_FOR } )" ,
37- ),
38- page .expect_response (
39- _DEFAULT_RESPONSE_TO_WAIT_FOR , timeout = _WAITING_FOR_SERVICE_TO_START
40- ),
161+ log_context (logging .INFO , "Go back to dashboard" ),
162+ page .expect_response (re .compile (r"/projects\?.+" )) as list_projects_response ,
163+ ):
164+ page .get_by_test_id ("dashboardBtn" ).click ()
165+ page .get_by_test_id ("confirmDashboardBtn" ).click ()
166+ assert (
167+ list_projects_response .value .ok
168+ ), f"Failed to list projects: { list_projects_response .value .status } "
169+ project_listing = list_projects_response .value .json ()
170+ assert "data" in project_listing
171+ assert len (project_listing ["data" ]) > 0
172+ # find the project we just created, it's the first one
173+ our_project = project_listing ["data" ][0 ]
174+ assert (
175+ our_project ["name" ] == _STUDY_FUNCTION_NAME
176+ ), f"Expected to find our project named { _STUDY_FUNCTION_NAME } in { project_listing } "
177+
178+ # 3. convert it to a function
179+ create_function_from_project (page , our_project ["uuid" ])
180+
181+ # 3. start a RSM with that function
182+
183+ with log_context (
184+ logging .INFO ,
185+ f"Waiting for { service_key } to be responsive (waiting for { _DEFAULT_RESPONSE_TO_WAIT_FOR } )" ,
41186 ):
42187 project_data = create_project_from_service_dashboard (
43188 ServiceType .DYNAMIC , service_key , None , service_version
@@ -58,3 +203,26 @@ def test_response_surface_modeling(
58203 product_url = product_url ,
59204 is_service_legacy = is_service_legacy ,
60205 )
206+
207+ service_iframe = page .frame_locator ("iframe" )
208+ with log_context (logging .INFO , "Waiting for the RSM to be ready..." ):
209+ service_iframe .get_by_role ("grid" ).wait_for (
210+ state = "visible" , timeout = _WAITING_FOR_SERVICE_TO_APPEAR
211+ )
212+
213+ page .wait_for_timeout (10000 )
214+
215+ # # select the function
216+ # service_iframe.get_by_role("gridcell", name=_FUNCTION_NAME).click()
217+
218+ # # Find the first input field (textbox) in the iframe
219+ # min_input_field = service_iframe.get_by_role("textbox").nth(0)
220+ # min_input_field.fill("1")
221+ # max_input_field = service_iframe.get_by_role("textbox").nth(1)
222+ # max_input_field.fill("10")
223+
224+ # # click on next
225+ # service_iframe.get_by_role("button", name="Next").click()
226+
227+ # # then we wait a long time
228+ # page.wait_for_timeout(1 * MINUTE)
0 commit comments