Skip to content

Commit c83d60c

Browse files
🎨 Improving E2E tests: expected_service_running (#6739)
1 parent 7ec1d25 commit c83d60c

File tree

6 files changed

+85
-17
lines changed

6 files changed

+85
-17
lines changed

packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
from collections import defaultdict
66
from collections.abc import Generator, Iterator
77
from dataclasses import dataclass, field
8+
from datetime import UTC, datetime, timedelta
89
from enum import Enum, unique
910
from typing import Any, Final
1011

12+
import httpx
1113
from playwright.sync_api import FrameLocator, Page, Request
1214
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
1315
from playwright.sync_api import WebSocket
16+
from pydantic import AnyUrl
1417
from pytest_simcore.helpers.logging_tools import log_context
1518

1619
SECOND: Final[int] = 1000
@@ -196,9 +199,11 @@ def __call__(self, message: str) -> None:
196199
class SocketIONodeProgressCompleteWaiter:
197200
node_id: str
198201
logger: logging.Logger
202+
product_url: AnyUrl
199203
_current_progress: dict[NodeProgressType, float] = field(
200204
default_factory=defaultdict
201205
)
206+
_last_poll_timestamp: datetime = field(default_factory=lambda: datetime.now(tz=UTC))
202207

203208
def __call__(self, message: str) -> bool:
204209
# socket.io encodes messages like so
@@ -234,6 +239,27 @@ def __call__(self, message: str) -> bool:
234239
round(progress, 1) == 1.0
235240
for progress in self._current_progress.values()
236241
)
242+
243+
_current_timestamp = datetime.now(UTC)
244+
if _current_timestamp - self._last_poll_timestamp > timedelta(seconds=5):
245+
url = f"https://{self.node_id}.services.{self.get_partial_product_url()}"
246+
response = httpx.get(url, timeout=10)
247+
self.logger.info(
248+
"Querying the service endpoint from the E2E test. Url: %s Response: %s",
249+
url,
250+
response,
251+
)
252+
if response.status_code <= 401:
253+
# NOTE: If the response status is less than 400, it means that the backend is ready (There are some services that respond with a 3XX)
254+
# MD: for now I have included 401 - as this also means that backend is ready
255+
if self.got_expected_node_progress_types():
256+
self.logger.warning(
257+
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
258+
self.get_current_progress(),
259+
)
260+
return True
261+
self._last_poll_timestamp = datetime.now(UTC)
262+
237263
return False
238264

239265
def got_expected_node_progress_types(self):
@@ -245,6 +271,9 @@ def got_expected_node_progress_types(self):
245271
def get_current_progress(self):
246272
return self._current_progress.values()
247273

274+
def get_partial_product_url(self):
275+
return f"{self.product_url}".split("//")[1]
276+
248277

249278
def wait_for_pipeline_state(
250279
current_state: RunningState,
@@ -332,9 +361,12 @@ def expected_service_running(
332361
websocket: WebSocket,
333362
timeout: int,
334363
press_start_button: bool,
364+
product_url: AnyUrl,
335365
) -> Generator[ServiceRunning, None, None]:
336366
with log_context(logging.INFO, msg="Waiting for node to run") as ctx:
337-
waiter = SocketIONodeProgressCompleteWaiter(node_id=node_id, logger=ctx.logger)
367+
waiter = SocketIONodeProgressCompleteWaiter(
368+
node_id=node_id, logger=ctx.logger, product_url=product_url
369+
)
338370
service_running = ServiceRunning(iframe_locator=None)
339371

340372
try:
@@ -366,12 +398,15 @@ def wait_for_service_running(
366398
websocket: WebSocket,
367399
timeout: int,
368400
press_start_button: bool,
401+
product_url: AnyUrl,
369402
) -> FrameLocator:
370403
"""NOTE: if the service was already started this will not work as some of the required websocket events will not be emitted again
371404
In which case this will need further adjutment"""
372405

373406
with log_context(logging.INFO, msg="Waiting for node to run") as ctx:
374-
waiter = SocketIONodeProgressCompleteWaiter(node_id=node_id, logger=ctx.logger)
407+
waiter = SocketIONodeProgressCompleteWaiter(
408+
node_id=node_id, logger=ctx.logger, product_url=product_url
409+
)
375410
with websocket.expect_event("framereceived", waiter, timeout=timeout):
376411
if press_start_button:
377412
_trigger_service_start(page, node_id)

packages/pytest-simcore/src/pytest_simcore/helpers/playwright_sim4life.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66

77
import arrow
88
from playwright.sync_api import FrameLocator, Page, WebSocket, expect
9-
from pydantic import TypeAdapter # pylint: disable=no-name-in-module
10-
from pydantic import ByteSize
9+
from pydantic import AnyUrl, ByteSize, TypeAdapter # pylint: disable=no-name-in-module
1110

1211
from .logging_tools import log_context
1312
from .playwright import (
@@ -105,6 +104,7 @@ def wait_for_launched_s4l(
105104
*,
106105
autoscaled: bool,
107106
copy_workspace: bool,
107+
product_url: AnyUrl,
108108
) -> WaitForS4LDict:
109109
with log_context(logging.INFO, "launch S4L") as ctx:
110110
predicate = S4LWaitForWebsocket(logger=ctx.logger)
@@ -130,6 +130,7 @@ def wait_for_launched_s4l(
130130
)
131131
+ (_S4L_COPY_WORKSPACE_TIME if copy_workspace else 0),
132132
press_start_button=False,
133+
product_url=product_url,
133134
)
134135
s4l_websocket = ws_info.value
135136
ctx.logger.info("acquired S4L websocket!")

tests/e2e-playwright/requirements/_test.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ pytest-runner
1313
pytest-sugar
1414
pyyaml
1515
tenacity
16+
httpx

tests/e2e-playwright/requirements/_test.txt

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.11
3+
# by the following command:
4+
#
5+
# pip-compile _test.in
6+
#
17
annotated-types==0.7.0
28
# via pydantic
9+
anyio==4.6.2.post1
10+
# via httpx
311
arrow==1.3.0
4-
# via -r requirements/_test.in
12+
# via -r _test.in
513
certifi==2024.8.30
6-
# via requests
14+
# via
15+
# httpcore
16+
# httpx
17+
# requests
718
charset-normalizer==3.3.2
819
# via requests
920
dnspython==2.6.1
1021
# via email-validator
1122
docker==7.1.0
12-
# via -r requirements/_test.in
23+
# via -r _test.in
1324
email-validator==2.2.0
1425
# via pydantic
1526
faker==29.0.0
16-
# via -r requirements/_test.in
27+
# via -r _test.in
1728
greenlet==3.0.3
1829
# via playwright
30+
h11==0.14.0
31+
# via httpcore
32+
httpcore==1.0.7
33+
# via httpx
34+
httpx==0.27.2
35+
# via -r _test.in
1936
idna==3.10
2037
# via
38+
# anyio
2139
# email-validator
40+
# httpx
2241
# requests
2342
iniconfig==2.0.0
2443
# via pytest
@@ -34,8 +53,8 @@ playwright==1.47.0
3453
# via pytest-playwright
3554
pluggy==1.5.0
3655
# via pytest
37-
pydantic==2.9.2
38-
# via -r requirements/_test.in
56+
pydantic[email]==2.9.2
57+
# via -r _test.in
3958
pydantic-core==2.23.4
4059
# via pydantic
4160
pyee==12.0.0
@@ -51,33 +70,37 @@ pytest==8.3.3
5170
pytest-base-url==2.1.0
5271
# via pytest-playwright
5372
pytest-html==4.1.1
54-
# via -r requirements/_test.in
73+
# via -r _test.in
5574
pytest-instafail==0.5.0
56-
# via -r requirements/_test.in
75+
# via -r _test.in
5776
pytest-metadata==3.1.1
5877
# via pytest-html
5978
pytest-playwright==0.5.2
60-
# via -r requirements/_test.in
79+
# via -r _test.in
6180
pytest-runner==6.0.1
62-
# via -r requirements/_test.in
81+
# via -r _test.in
6382
pytest-sugar==1.0.0
64-
# via -r requirements/_test.in
83+
# via -r _test.in
6584
python-dateutil==2.9.0.post0
6685
# via
6786
# arrow
6887
# faker
6988
python-slugify==8.0.4
7089
# via pytest-playwright
7190
pyyaml==6.0.2
72-
# via -r requirements/_test.in
91+
# via -r _test.in
7392
requests==2.32.3
7493
# via
7594
# docker
7695
# pytest-base-url
7796
six==1.16.0
7897
# via python-dateutil
98+
sniffio==1.3.1
99+
# via
100+
# anyio
101+
# httpx
79102
tenacity==9.0.0
80-
# via -r requirements/_test.in
103+
# via -r _test.in
81104
termcolor==2.4.0
82105
# via pytest-sugar
83106
text-unidecode==1.3

tests/e2e-playwright/tests/sim4life/test_sim4life.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any
1212

1313
from playwright.sync_api import Page, WebSocket
14+
from pydantic import AnyUrl
1415
from pytest_simcore.helpers.playwright import (
1516
ServiceType,
1617
web_socket_default_log_handler,
@@ -33,6 +34,7 @@ def test_sim4life(
3334
use_plus_button: bool,
3435
is_autoscaled: bool,
3536
check_videostreaming: bool,
37+
product_url: AnyUrl,
3638
):
3739
if use_plus_button:
3840
project_data = create_project_from_new_button(service_key)
@@ -54,6 +56,7 @@ def test_sim4life(
5456
log_in_and_out,
5557
autoscaled=is_autoscaled,
5658
copy_workspace=False,
59+
product_url=product_url,
5760
)
5861
s4l_websocket = resp["websocket"]
5962
with web_socket_default_log_handler(s4l_websocket):

tests/e2e-playwright/tests/tip/test_ti_plan.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from typing import Any, Final
1515

1616
from playwright.sync_api import Page, WebSocket
17+
from pydantic import AnyUrl
1718
from pytest_simcore.helpers.logging_tools import log_context
1819
from pytest_simcore.helpers.playwright import (
1920
MINUTE,
@@ -92,6 +93,7 @@ def test_classic_ti_plan( # noqa: PLR0915
9293
is_autoscaled: bool,
9394
is_product_lite: bool,
9495
create_tip_plan_from_dashboard: Callable[[str], dict[str, Any]],
96+
product_url: AnyUrl,
9597
):
9698
with log_context(logging.INFO, "Checking 'Access TIP' teaser"):
9799
# click to open and expand
@@ -141,6 +143,7 @@ def test_classic_ti_plan( # noqa: PLR0915
141143
else _ELECTRODE_SELECTOR_MAX_STARTUP_TIME
142144
),
143145
press_start_button=False,
146+
product_url=product_url,
144147
)
145148
# NOTE: Sometimes this iframe flicks and shows a white page. This wait will avoid it
146149
page.wait_for_timeout(_ELECTRODE_SELECTOR_FLICKERING_WAIT_TIME)
@@ -200,6 +203,7 @@ def test_classic_ti_plan( # noqa: PLR0915
200203
else _JLAB_MAX_STARTUP_MAX_TIME
201204
),
202205
press_start_button=False,
206+
product_url=product_url,
203207
) as service_running:
204208
app_mode_trigger_next_app(page)
205209
ti_iframe = service_running.iframe_locator
@@ -284,6 +288,7 @@ def test_classic_ti_plan( # noqa: PLR0915
284288
else _POST_PRO_MAX_STARTUP_TIME
285289
),
286290
press_start_button=False,
291+
product_url=product_url,
287292
) as service_running:
288293
app_mode_trigger_next_app(page)
289294
s4l_postpro_iframe = service_running.iframe_locator

0 commit comments

Comments
 (0)