8
8
from dataclasses import dataclass , field
9
9
from datetime import UTC , datetime , timedelta
10
10
from enum import Enum , unique
11
+ from pathlib import Path
11
12
from typing import Any , Final
12
13
13
14
import pytest
14
15
from 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
+ )
16
26
from playwright .sync_api import TimeoutError as PlaywrightTimeoutError
17
- from playwright .sync_api import WebSocket
27
+ from playwright .sync_api import (
28
+ WebSocket ,
29
+ )
18
30
from pydantic import AnyUrl
19
31
20
32
from .logging_tools import log_context
@@ -112,6 +124,9 @@ class SocketIOEvent:
112
124
name : str
113
125
obj : dict [str , Any ]
114
126
127
+ def to_json (self ) -> str :
128
+ return json .dumps ({"name" : self .name , "obj" : self .obj })
129
+
115
130
116
131
SOCKETIO_MESSAGE_PREFIX : Final [str ] = "42"
117
132
@@ -325,20 +340,20 @@ class SocketIONodeProgressCompleteWaiter:
325
340
product_url : AnyUrl
326
341
api_request_context : APIRequestContext
327
342
is_service_legacy : bool
343
+ assertion_output_folder : Path
328
344
_current_progress : dict [NodeProgressType , float ] = field (
329
345
default_factory = defaultdict
330
346
)
331
347
_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 )
333
349
_service_ready : bool = False
334
350
335
351
def __call__ (self , message : str ) -> bool :
336
352
# socket.io encodes messages like so
337
353
# https://stackoverflow.com/questions/24564877/what-do-these-numbers-mean-in-socket-io-payload
338
354
if message .startswith (SOCKETIO_MESSAGE_PREFIX ):
339
- self ._number_of_messages_received += 1
340
355
decoded_message = decode_socketio_42_message (message )
341
- self .logger . info ( "Received message: %s" , decoded_message . name )
356
+ self ._received_messages . append ( decoded_message )
342
357
if (
343
358
(decoded_message .name == _OSparcMessages .SERVICE_STATUS .value )
344
359
and (decoded_message .obj ["service_uuid" ] == self .node_id )
@@ -392,8 +407,12 @@ def __call__(self, message: str) -> bool:
392
407
url = (
393
408
f"https://{ self .node_id } .services.{ self .get_partial_product_url ()} "
394
409
)
395
- with contextlib .suppress (PlaywrightTimeoutError , TimeoutError ):
410
+ response = None
411
+ with contextlib .suppress (
412
+ PlaywrightTimeoutError , TimeoutError , PlaywrightError
413
+ ):
396
414
response = self .api_request_context .get (url , timeout = 5000 )
415
+ if response :
397
416
self .logger .log (
398
417
(
399
418
logging .ERROR
@@ -418,11 +437,11 @@ def __call__(self, message: str) -> bool:
418
437
)
419
438
self ._service_ready = True
420
439
return True
421
- self ._last_poll_timestamp = datetime .now (UTC )
440
+ self ._last_poll_timestamp = datetime .now (UTC )
422
441
423
442
return False
424
443
425
- def got_expected_node_progress_types (self ):
444
+ def got_expected_node_progress_types (self ) -> bool :
426
445
return all (
427
446
progress_type in self ._current_progress
428
447
for progress_type in NodeProgressType .required_types_for_started_service ()
@@ -431,12 +450,28 @@ def got_expected_node_progress_types(self):
431
450
def get_current_progress (self ):
432
451
return self ._current_progress .values ()
433
452
434
- def get_partial_product_url (self ):
453
+ def get_partial_product_url (self ) -> str :
435
454
return f"{ self .product_url } " .split ("//" )[1 ]
436
455
437
456
@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
+ )
440
475
441
476
442
477
_FAIL_FAST_COMPUTATIONAL_STATES : Final [tuple [RunningState , ...]] = (
@@ -510,6 +545,7 @@ def expected_service_running(
510
545
press_start_button : bool ,
511
546
product_url : AnyUrl ,
512
547
is_service_legacy : bool ,
548
+ assertion_output_folder : Path ,
513
549
) -> Generator [ServiceRunning , None , None ]:
514
550
with log_context (
515
551
logging .INFO , msg = f"Waiting for node to run. Timeout: { timeout } "
@@ -520,6 +556,7 @@ def expected_service_running(
520
556
product_url = product_url ,
521
557
api_request_context = page .request ,
522
558
is_service_legacy = is_service_legacy ,
559
+ assertion_output_folder = assertion_output_folder ,
523
560
)
524
561
service_running = ServiceRunning (iframe_locator = None )
525
562
@@ -528,7 +565,7 @@ def expected_service_running(
528
565
_trigger_service_start (page , node_id )
529
566
530
567
yield service_running
531
- assert waiter .is_service_ready
568
+ waiter .assert_service_ready ()
532
569
service_running .iframe_locator = page .frame_locator (
533
570
f'[osparc-test-id="iframe_{ node_id } "]'
534
571
)
@@ -543,6 +580,7 @@ def wait_for_service_running(
543
580
press_start_button : bool ,
544
581
product_url : AnyUrl ,
545
582
is_service_legacy : bool ,
583
+ assertion_output_folder : Path ,
546
584
) -> FrameLocator :
547
585
"""NOTE: if the service was already started this will not work as some of the required websocket events will not be emitted again
548
586
In which case this will need further adjutment"""
@@ -556,11 +594,13 @@ def wait_for_service_running(
556
594
product_url = product_url ,
557
595
api_request_context = page .request ,
558
596
is_service_legacy = is_service_legacy ,
597
+ assertion_output_folder = assertion_output_folder ,
559
598
)
560
599
with websocket .expect_event ("framereceived" , waiter , timeout = timeout ):
561
600
if press_start_button :
562
601
_trigger_service_start (page , node_id )
563
- assert waiter .is_service_ready
602
+
603
+ waiter .assert_service_ready ()
564
604
return page .frame_locator (f'[osparc-test-id="iframe_{ node_id } "]' )
565
605
566
606
0 commit comments