88from dataclasses import dataclass , field
99from datetime import UTC , datetime , timedelta
1010from enum import Enum , unique
11+ from pathlib import Path
1112from typing import Any , Final
1213
1314import pytest
1415from playwright ._impl ._sync_base import EventContextManager
15- from playwright .sync_api import APIRequestContext , FrameLocator , Locator , Page , Request
16+ from playwright .sync_api import (
17+ APIRequestContext ,
18+ )
19+ from playwright .sync_api import Error as PlaywrightError
20+ from playwright .sync_api import (
21+ FrameLocator ,
22+ Locator ,
23+ Page ,
24+ Request ,
25+ )
1626from playwright .sync_api import TimeoutError as PlaywrightTimeoutError
17- from playwright .sync_api import WebSocket
27+ from playwright .sync_api import (
28+ WebSocket ,
29+ )
1830from pydantic import AnyUrl
1931
2032from .logging_tools import log_context
@@ -112,6 +124,9 @@ class SocketIOEvent:
112124 name : str
113125 obj : dict [str , Any ]
114126
127+ def to_json (self ) -> str :
128+ return json .dumps ({"name" : self .name , "obj" : self .obj })
129+
115130
116131SOCKETIO_MESSAGE_PREFIX : Final [str ] = "42"
117132
@@ -325,20 +340,20 @@ class SocketIONodeProgressCompleteWaiter:
325340 product_url : AnyUrl
326341 api_request_context : APIRequestContext
327342 is_service_legacy : bool
343+ assertion_output_folder : Path
328344 _current_progress : dict [NodeProgressType , float ] = field (
329345 default_factory = defaultdict
330346 )
331347 _last_poll_timestamp : datetime = field (default_factory = lambda : datetime .now (tz = UTC ))
332- _number_of_messages_received : int = 0
348+ _received_messages : list [ SocketIOEvent ] = field ( default_factory = list )
333349 _service_ready : bool = False
334350
335351 def __call__ (self , message : str ) -> bool :
336352 # socket.io encodes messages like so
337353 # https://stackoverflow.com/questions/24564877/what-do-these-numbers-mean-in-socket-io-payload
338354 if message .startswith (SOCKETIO_MESSAGE_PREFIX ):
339- self ._number_of_messages_received += 1
340355 decoded_message = decode_socketio_42_message (message )
341- self .logger . info ( "Received message: %s" , decoded_message . name )
356+ self ._received_messages . append ( decoded_message )
342357 if (
343358 (decoded_message .name == _OSparcMessages .SERVICE_STATUS .value )
344359 and (decoded_message .obj ["service_uuid" ] == self .node_id )
@@ -392,8 +407,12 @@ def __call__(self, message: str) -> bool:
392407 url = (
393408 f"https://{ self .node_id } .services.{ self .get_partial_product_url ()} "
394409 )
395- with contextlib .suppress (PlaywrightTimeoutError , TimeoutError ):
410+ response = None
411+ with contextlib .suppress (
412+ PlaywrightTimeoutError , TimeoutError , PlaywrightError
413+ ):
396414 response = self .api_request_context .get (url , timeout = 5000 )
415+ if response :
397416 self .logger .log (
398417 (
399418 logging .ERROR
@@ -418,11 +437,11 @@ def __call__(self, message: str) -> bool:
418437 )
419438 self ._service_ready = True
420439 return True
421- self ._last_poll_timestamp = datetime .now (UTC )
440+ self ._last_poll_timestamp = datetime .now (UTC )
422441
423442 return False
424443
425- def got_expected_node_progress_types (self ):
444+ def got_expected_node_progress_types (self ) -> bool :
426445 return all (
427446 progress_type in self ._current_progress
428447 for progress_type in NodeProgressType .required_types_for_started_service ()
@@ -431,12 +450,28 @@ def got_expected_node_progress_types(self):
431450 def get_current_progress (self ):
432451 return self ._current_progress .values ()
433452
434- def get_partial_product_url (self ):
453+ def get_partial_product_url (self ) -> str :
435454 return f"{ self .product_url } " .split ("//" )[1 ]
436455
437456 @property
438- def is_service_ready (self ) -> bool :
439- return self ._service_ready
457+ def number_received_messages (self ) -> int :
458+ return len (self ._received_messages )
459+
460+ def assert_service_ready (self ) -> None :
461+ if not self ._service_ready :
462+ with self .assertion_output_folder .joinpath ("websocket.json" ).open ("w" ) as f :
463+ f .writelines ("[" )
464+ f .writelines (
465+ f"{ msg .to_json ()} ," for msg in self ._received_messages [:- 1 ]
466+ )
467+ f .writelines (
468+ f"{ self ._received_messages [- 1 ].to_json ()} "
469+ ) # no comma for last element
470+ f .writelines ("]" )
471+ assert self ._service_ready , (
472+ f"the service failed and received { self .number_received_messages } websocket messages while waiting!"
473+ "\n TIP: check websocket.log for detailed information in the test-results folder!"
474+ )
440475
441476
442477_FAIL_FAST_COMPUTATIONAL_STATES : Final [tuple [RunningState , ...]] = (
@@ -510,6 +545,7 @@ def expected_service_running(
510545 press_start_button : bool ,
511546 product_url : AnyUrl ,
512547 is_service_legacy : bool ,
548+ assertion_output_folder : Path ,
513549) -> Generator [ServiceRunning , None , None ]:
514550 with log_context (
515551 logging .INFO , msg = f"Waiting for node to run. Timeout: { timeout } "
@@ -520,6 +556,7 @@ def expected_service_running(
520556 product_url = product_url ,
521557 api_request_context = page .request ,
522558 is_service_legacy = is_service_legacy ,
559+ assertion_output_folder = assertion_output_folder ,
523560 )
524561 service_running = ServiceRunning (iframe_locator = None )
525562
@@ -528,7 +565,7 @@ def expected_service_running(
528565 _trigger_service_start (page , node_id )
529566
530567 yield service_running
531- assert waiter .is_service_ready
568+ waiter .assert_service_ready ()
532569 service_running .iframe_locator = page .frame_locator (
533570 f'[osparc-test-id="iframe_{ node_id } "]'
534571 )
@@ -543,6 +580,7 @@ def wait_for_service_running(
543580 press_start_button : bool ,
544581 product_url : AnyUrl ,
545582 is_service_legacy : bool ,
583+ assertion_output_folder : Path ,
546584) -> FrameLocator :
547585 """NOTE: if the service was already started this will not work as some of the required websocket events will not be emitted again
548586 In which case this will need further adjutment"""
@@ -556,11 +594,13 @@ def wait_for_service_running(
556594 product_url = product_url ,
557595 api_request_context = page .request ,
558596 is_service_legacy = is_service_legacy ,
597+ assertion_output_folder = assertion_output_folder ,
559598 )
560599 with websocket .expect_event ("framereceived" , waiter , timeout = timeout ):
561600 if press_start_button :
562601 _trigger_service_start (page , node_id )
563- assert waiter .is_service_ready
602+
603+ waiter .assert_service_ready ()
564604 return page .frame_locator (f'[osparc-test-id="iframe_{ node_id } "]' )
565605
566606
0 commit comments