From d4516307dfb112f7d64dae82a6156cc552b0df5f Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 10:48:48 +0100 Subject: [PATCH 1/9] improving e2e tests --- .../src/pytest_simcore/helpers/playwright.py | 145 +++++++++++++----- .../helpers/playwright_sim4life.py | 3 +- tests/e2e-playwright/Makefile | 15 +- tests/e2e-playwright/tests/conftest.py | 18 +-- .../tests/jupyterlabs/test_jupyterlab.py | 16 +- .../tests/osparc_platform/conftest.py | 0 .../tests/osparc_platform/test_platform.py | 135 ++++++++++++++++ .../tests/sim4life/test_sim4life.py | 18 +-- .../tests/sim4life/test_template.py | 16 +- .../tests/sleepers/test_sleepers.py | 5 +- .../e2e-playwright/tests/tip/test_ti_plan.py | 61 ++++++-- 11 files changed, 340 insertions(+), 92 deletions(-) create mode 100644 tests/e2e-playwright/tests/osparc_platform/conftest.py create mode 100644 tests/e2e-playwright/tests/osparc_platform/test_platform.py diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py index f063b6efd61..6a470ec1b0e 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py @@ -2,19 +2,25 @@ import json import logging import re +import typing from collections import defaultdict -from collections.abc import Generator, Iterator +from collections.abc import Generator from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta from enum import Enum, unique from typing import Any, Final import httpx +from playwright._impl._sync_base import EventContextManager from playwright.sync_api import FrameLocator, Page, Request from playwright.sync_api import TimeoutError as PlaywrightTimeoutError from playwright.sync_api import WebSocket from pydantic import AnyUrl -from pytest_simcore.helpers.logging_tools import log_context + +from .logging_tools import log_context + +_logger = logging.getLogger(__name__) + SECOND: Final[int] = 1000 MINUTE: Final[int] = 60 * SECOND @@ -106,6 +112,94 @@ class SocketIOEvent: SOCKETIO_MESSAGE_PREFIX: Final[str] = "42" +@dataclass +class RestartableWebSocket: + page: Page + ws: WebSocket + _registered_events: list[tuple[str, typing.Callable | None]] = field( + default_factory=list + ) + _number_of_restarts: int = 0 + + def __post_init__(self): + self._configure_websocket_events() + + def _configure_websocket_events(self): + try: + with log_context( + logging.DEBUG, + msg="handle websocket message (set to --log-cli-level=DEBUG level if you wanna see all of them)", + ) as ctx: + + def on_framesent(payload: str | bytes) -> None: + ctx.logger.debug("⬇️ Frame sent: %s", payload) + + def on_framereceived(payload: str | bytes) -> None: + ctx.logger.debug("⬆️ Frame received: %s", payload) + + def on_close(_: WebSocket) -> None: + ctx.logger.warning( + "⚠️ WebSocket closed. Attempting to reconnect..." + ) + self._attempt_reconnect(ctx.logger) + + def on_socketerror(error_msg: str) -> None: + ctx.logger.error("❌ WebSocket error: %s", error_msg) + + # Attach core event listeners + self.ws.on("framesent", on_framesent) + self.ws.on("framereceived", on_framereceived) + self.ws.on("close", on_close) + self.ws.on("socketerror", on_socketerror) + + finally: + # Detach core event listeners + self.ws.remove_listener("framesent", on_framesent) + self.ws.remove_listener("framereceived", on_framereceived) + self.ws.remove_listener("close", on_close) + self.ws.remove_listener("socketerror", on_socketerror) + + def _attempt_reconnect(self, logger: logging.Logger) -> None: + """ + Attempt to reconnect the WebSocket and restore event listeners. + """ + try: + with self.page.expect_websocket() as ws_info: + assert not ws_info.value.is_closed() + + self.ws = ws_info.value + self._number_of_restarts += 1 + logger.info( + "🔄 Reconnected to WebSocket successfully. Number of reconnections: %s", + self._number_of_restarts, + ) + self._configure_websocket_events() + # Re-register all custom event listeners + for event, predicate in self._registered_events: + self.ws.expect_event(event, predicate) + + except Exception as e: + logger.error("🚨 Failed to reconnect WebSocket: %s", e) + + def expect_event( + self, + event: str, + predicate: typing.Callable | None = None, + *, + timeout: float | None = None, + ) -> EventContextManager: + """ + Register an event listener with support for reconnection. + """ + output = self.ws.expect_event(event, predicate, timeout=timeout) + self._registered_events.append((event, predicate)) + return output + + @classmethod + def create(cls, page: Page, ws: WebSocket): + return cls(page, ws) + + def decode_socketio_42_message(message: str) -> SocketIOEvent: data = json.loads(message.removeprefix(SOCKETIO_MESSAGE_PREFIX)) return SocketIOEvent(name=data[0], obj=data[1]) @@ -278,7 +372,7 @@ def get_partial_product_url(self): def wait_for_pipeline_state( current_state: RunningState, *, - websocket: WebSocket, + websocket: RestartableWebSocket, if_in_states: tuple[RunningState, ...], expected_states: tuple[RunningState, ...], timeout_ms: int, @@ -301,39 +395,6 @@ def wait_for_pipeline_state( return current_state -@contextlib.contextmanager -def web_socket_default_log_handler(web_socket: WebSocket) -> Iterator[None]: - - try: - with log_context( - logging.DEBUG, - msg="handle websocket message (set to --log-cli-level=DEBUG level if you wanna see all of them)", - ) as ctx: - - def on_framesent(payload: str | bytes) -> None: - ctx.logger.debug("⬇️ Frame sent: %s", payload) - - def on_framereceived(payload: str | bytes) -> None: - ctx.logger.debug("⬆️ Frame received: %s", payload) - - def on_close(payload: WebSocket) -> None: - ctx.logger.warning("⚠️ Websocket closed: %s", payload) - - def on_socketerror(error_msg: str) -> None: - ctx.logger.error("❌ Websocket error: %s", error_msg) - - web_socket.on("framesent", on_framesent) - web_socket.on("framereceived", on_framereceived) - web_socket.on("close", on_close) - web_socket.on("socketerror", on_socketerror) - yield - finally: - web_socket.remove_listener("framesent", on_framesent) - web_socket.remove_listener("framereceived", on_framereceived) - web_socket.remove_listener("close", on_close) - web_socket.remove_listener("socketerror", on_socketerror) - - def _node_started_predicate(request: Request) -> bool: return bool( re.search(NODE_START_REQUEST_PATTERN, request.url) @@ -358,12 +419,14 @@ def expected_service_running( *, page: Page, node_id: str, - websocket: WebSocket, + websocket: RestartableWebSocket, timeout: int, press_start_button: bool, product_url: AnyUrl, ) -> Generator[ServiceRunning, None, None]: - with log_context(logging.INFO, msg="Waiting for node to run") as ctx: + with log_context( + logging.INFO, msg=f"Waiting for node to run. Timeout: {timeout}" + ) as ctx: waiter = SocketIONodeProgressCompleteWaiter( node_id=node_id, logger=ctx.logger, product_url=product_url ) @@ -395,7 +458,7 @@ def wait_for_service_running( *, page: Page, node_id: str, - websocket: WebSocket, + websocket: RestartableWebSocket, timeout: int, press_start_button: bool, product_url: AnyUrl, @@ -403,7 +466,9 @@ def wait_for_service_running( """NOTE: if the service was already started this will not work as some of the required websocket events will not be emitted again In which case this will need further adjutment""" - with log_context(logging.INFO, msg="Waiting for node to run") as ctx: + with log_context( + logging.INFO, msg=f"Waiting for node to run. Timeout: {timeout}" + ) as ctx: waiter = SocketIONodeProgressCompleteWaiter( node_id=node_id, logger=ctx.logger, product_url=product_url ) diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright_sim4life.py b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright_sim4life.py index c59718f4aff..8c5b74d032b 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright_sim4life.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright_sim4life.py @@ -13,6 +13,7 @@ MINUTE, SECOND, SOCKETIO_MESSAGE_PREFIX, + RestartableWebSocket, SocketIOEvent, decode_socketio_42_message, wait_for_service_running, @@ -100,7 +101,7 @@ class WaitForS4LDict(TypedDict): def wait_for_launched_s4l( page: Page, node_id, - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, *, autoscaled: bool, copy_workspace: bool, diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 88a15a845d1..55b46c4c0b4 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -107,6 +107,19 @@ test-sleepers-dev: _check_venv_active ## runs sleepers test on local deploy $(CURDIR)/tests/sleepers/test_sleepers.py +.PHONY: test-osparc-platform-dev +test-osparc-platform-dev: _check_venv_active + @PWDEBUG=1 pytest \ + -sxvv \ + --color=yes \ + --pdb \ + --product-url=http://$(get_my_ip):9081 \ + --headed \ + --autoregister \ + --tracing=on \ + $(CURDIR)/tests/osparc_platform/test_platform.py + + # Define the files where user input will be saved SLEEPERS_INPUT_FILE := .e2e-playwright-sleepers-env.txt S4L_INPUT_FILE := .e2e-playwright-sim4life-env.txt @@ -172,7 +185,7 @@ define run_test pytest -s $2 \ --color=yes \ --browser chromium \ - --headed \ + --headed \ $$TEST_ARGS endef diff --git a/tests/e2e-playwright/tests/conftest.py b/tests/e2e-playwright/tests/conftest.py index 6fd15e8218c..551dc1ad266 100644 --- a/tests/e2e-playwright/tests/conftest.py +++ b/tests/e2e-playwright/tests/conftest.py @@ -20,7 +20,7 @@ import arrow import pytest from faker import Faker -from playwright.sync_api import APIRequestContext, BrowserContext, Page, WebSocket +from playwright.sync_api import APIRequestContext, BrowserContext, Page from playwright.sync_api._generated import Playwright from pydantic import AnyUrl, TypeAdapter from pytest_simcore.helpers.faker_factories import DEFAULT_TEST_PASSWORD @@ -29,13 +29,13 @@ MINUTE, SECOND, AutoRegisteredUser, + RestartableWebSocket, RunningState, ServiceType, SocketIOEvent, SocketIOProjectClosedWaiter, SocketIOProjectStateUpdatedWaiter, decode_socketio_42_message, - web_socket_default_log_handler, ) from pytest_simcore.helpers.pydantic_extension import Secret4TestsStr @@ -331,7 +331,7 @@ def log_in_and_out( user_password: Secret4TestsStr, auto_register: bool, register: Callable[[], AutoRegisteredUser], -) -> Iterator[WebSocket]: +) -> Iterator[RestartableWebSocket]: with log_context( logging.INFO, f"Open {product_url=} using {user_name=}/{user_password=}/{auto_register=}", @@ -374,8 +374,8 @@ def log_in_and_out( page.get_by_test_id("loginSubmitBtn").click() assert response_info.value.ok, f"{response_info.value.json()}" - ws = ws_info.value - assert not ws.is_closed() + assert not ws_info.value.is_closed() + restartable_wb = RestartableWebSocket.create(page, ws_info.value) # Welcome to Sim4Life page.wait_for_timeout(5000) @@ -389,8 +389,8 @@ def log_in_and_out( if quickStartWindowCloseBtnLocator.is_visible(): quickStartWindowCloseBtnLocator.click() - with web_socket_default_log_handler(ws): - yield ws + # with web_socket_default_log_handler(ws): + yield restartable_wb with log_context( logging.INFO, @@ -408,7 +408,7 @@ def log_in_and_out( @pytest.fixture def create_new_project_and_delete( page: Page, - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, is_product_billable: bool, api_request_context: APIRequestContext, product_url: AnyUrl, @@ -660,7 +660,7 @@ def _( def start_and_stop_pipeline( product_url: AnyUrl, page: Page, - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, api_request_context: APIRequestContext, ) -> Iterator[Callable[[], SocketIOEvent]]: started_pipeline_ids = [] diff --git a/tests/e2e-playwright/tests/jupyterlabs/test_jupyterlab.py b/tests/e2e-playwright/tests/jupyterlabs/test_jupyterlab.py index 6afc2bb1f13..2451f1e3648 100644 --- a/tests/e2e-playwright/tests/jupyterlabs/test_jupyterlab.py +++ b/tests/e2e-playwright/tests/jupyterlabs/test_jupyterlab.py @@ -16,7 +16,12 @@ from playwright.sync_api import Page, WebSocket from pydantic import ByteSize from pytest_simcore.helpers.logging_tools import log_context -from pytest_simcore.helpers.playwright import MINUTE, SECOND, ServiceType +from pytest_simcore.helpers.playwright import ( + MINUTE, + SECOND, + RestartableWebSocket, + ServiceType, +) _WAITING_FOR_SERVICE_TO_START: Final[int] = ( 10 * MINUTE @@ -110,8 +115,11 @@ def test_jupyterlab( iframe.get_by_role("button", name="New Launcher").click() with page.expect_websocket(_JLabWaitForTerminalWebSocket()) as ws_info: iframe.get_by_label("Launcher").get_by_text("Terminal").click() - terminal_web_socket = ws_info.value - assert not terminal_web_socket.is_closed() + + assert not ws_info.value.is_closed() + restartable_terminal_web_socket = RestartableWebSocket.create( + page, ws_info.value + ) terminal = iframe.locator( "#jp-Terminal-0 > div > div.xterm-screen" @@ -122,7 +130,7 @@ def test_jupyterlab( terminal.press("Enter") # NOTE: this call creates a large file with random blocks inside blocks_count = int(large_file_size / large_file_block_size) - with terminal_web_socket.expect_event( + with restartable_terminal_web_socket.expect_event( "framereceived", _JLabTerminalWebSocketWaiter( expected_message_type="stdout", expected_message_contents="copied" diff --git a/tests/e2e-playwright/tests/osparc_platform/conftest.py b/tests/e2e-playwright/tests/osparc_platform/conftest.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/e2e-playwright/tests/osparc_platform/test_platform.py b/tests/e2e-playwright/tests/osparc_platform/test_platform.py new file mode 100644 index 00000000000..a5fad87df00 --- /dev/null +++ b/tests/e2e-playwright/tests/osparc_platform/test_platform.py @@ -0,0 +1,135 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments +# pylint: disable=too-many-statements +# pylint: disable=no-name-in-module + +# import logging +# import re +# from collections.abc import Callable, Iterator + +# import pytest +# from playwright.sync_api import BrowserContext, Page, WebSocket +# from pydantic import AnyUrl +# from pytest_simcore.helpers.logging_tools import log_context +# from pytest_simcore.helpers.playwright import ( +# AutoRegisteredUser, +# web_socket_default_log_handler, +# ) +# from pytest_simcore.helpers.pydantic_extension import Secret4TestsStr + + +# @pytest.fixture +# def log_in_and_out( +# page: Page, +# product_url: AnyUrl, +# user_name: str, +# user_password: Secret4TestsStr, +# auto_register: bool, +# register: Callable[[], AutoRegisteredUser], +# context: BrowserContext, +# ) -> Iterator[WebSocket]: +# with log_context( +# logging.INFO, +# f"Open {product_url=} using {user_name=}/{user_password=}/{auto_register=}", +# ): +# response = page.goto(f"{product_url}") +# assert response +# assert response.ok, response.body() + +# # In case the accept cookies or new release window shows up, we accept +# page.wait_for_timeout(2000) +# acceptCookiesBtnLocator = page.get_by_test_id("acceptCookiesBtn") +# if acceptCookiesBtnLocator.is_visible(): +# acceptCookiesBtnLocator.click() +# page.wait_for_timeout(1000) +# newReleaseCloseBtnLocator = page.get_by_test_id("newReleaseCloseBtn") +# if newReleaseCloseBtnLocator.is_visible(): +# newReleaseCloseBtnLocator.click() + +# with ( +# log_context( +# logging.INFO, +# f"Log in {product_url} using {user_name=}/{user_password=}/{auto_register=}", +# ), +# page.expect_websocket() as ws_info, +# ): +# if auto_register: +# register() +# else: +# with log_context( +# logging.INFO, +# f"Log in {product_url=} using {user_name=}/{user_password=}", +# ): +# _user_email_box = page.get_by_test_id("loginUserEmailFld") +# _user_email_box.click() +# _user_email_box.fill(user_name) +# _user_password_box = page.get_by_test_id("loginPasswordFld") +# _user_password_box.click() +# _user_password_box.fill(user_password.get_secret_value()) +# with page.expect_response(re.compile(r"/login")) as response_info: +# page.get_by_test_id("loginSubmitBtn").click() +# assert response_info.value.ok, f"{response_info.value.json()}" + +# assert not ws_info.value.is_closed() + + +# # Welcome to Sim4Life +# page.wait_for_timeout(5000) +# welcomeToSim4LifeLocator = page.get_by_text("Welcome to Sim4Life") +# if welcomeToSim4LifeLocator.is_visible(): +# page.get_by_text("").nth( +# 1 +# ).click() # There is missing osparc-test-id for this button +# # Quick start window +# quickStartWindowCloseBtnLocator = page.get_by_test_id("quickStartWindowCloseBtn") +# if quickStartWindowCloseBtnLocator.is_visible(): +# quickStartWindowCloseBtnLocator.click() + +# context.storage_state(path="state.json") + +# yield ws + +# with log_context( +# logging.INFO, +# f"Log out of {product_url=} using {user_name=}/{user_password=}", +# ): +# page.keyboard.press("Escape") +# page.get_by_test_id("userMenuBtn").click() +# with page.expect_response(re.compile(r"/auth/logout")) as response_info: +# page.get_by_test_id("userMenuLogoutBtn").click() +# assert response_info.value.ok, f"{response_info.value.json()}" +# # so we see the logout page +# page.wait_for_timeout(500) + + +# @pytest.fixture +# def logged_in_context(playwright): +# browser = playwright.chromium.launch(headless=False) +# context = browser.new_context(storage_state="state.json") +# yield context +# context.close() +# browser.close() + + +# def test_simple_folder_workflow(logged_in_context, product_url): +# page = logged_in_context.new_page() + +# page.goto(f"{product_url}") +# page.wait_for_timeout(1000) +# page.get_by_test_id("dashboard").get_by_text("New folder", exact=True).click() +# page.get_by_placeholder("Title").fill("My new folder") +# page.get_by_placeholder("Title").press("Enter") + +# page.get_by_test_id("dashboard").get_by_text("My new folder").click() +# page.get_by_test_id("contextTree").get_by_text("My Workspace").click() + + +# def test_simple_workspace_workflow(logged_in_context, product_url): +# page = logged_in_context.new_page() + +# page.goto(f"{product_url}") +# page.wait_for_timeout(1000) +# page.get_by_test_id("userMenuBtn").click() +# page.wait_for_timeout(1000) diff --git a/tests/e2e-playwright/tests/sim4life/test_sim4life.py b/tests/e2e-playwright/tests/sim4life/test_sim4life.py index 924e6efa535..28b6ecf56a7 100644 --- a/tests/e2e-playwright/tests/sim4life/test_sim4life.py +++ b/tests/e2e-playwright/tests/sim4life/test_sim4life.py @@ -10,12 +10,9 @@ from collections.abc import Callable from typing import Any -from playwright.sync_api import Page, WebSocket +from playwright.sync_api import Page from pydantic import AnyUrl -from pytest_simcore.helpers.playwright import ( - ServiceType, - web_socket_default_log_handler, -) +from pytest_simcore.helpers.playwright import RestartableWebSocket, ServiceType from pytest_simcore.helpers.playwright_sim4life import ( check_video_streaming, interact_with_s4l, @@ -29,7 +26,7 @@ def test_sim4life( [ServiceType, str, str | None], dict[str, Any] ], create_project_from_new_button: Callable[[str], dict[str, Any]], - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, service_key: str, use_plus_button: bool, is_autoscaled: bool, @@ -59,9 +56,8 @@ def test_sim4life( product_url=product_url, ) s4l_websocket = resp["websocket"] - with web_socket_default_log_handler(s4l_websocket): - s4l_iframe = resp["iframe"] - interact_with_s4l(page, s4l_iframe) + s4l_iframe = resp["iframe"] + interact_with_s4l(page, s4l_iframe) - if check_videostreaming: - check_video_streaming(page, s4l_iframe, s4l_websocket) + if check_videostreaming: + check_video_streaming(page, s4l_iframe, s4l_websocket) diff --git a/tests/e2e-playwright/tests/sim4life/test_template.py b/tests/e2e-playwright/tests/sim4life/test_template.py index fb9b260c992..d4200611ab4 100644 --- a/tests/e2e-playwright/tests/sim4life/test_template.py +++ b/tests/e2e-playwright/tests/sim4life/test_template.py @@ -10,8 +10,8 @@ from collections.abc import Callable from typing import Any -from playwright.sync_api import Page, WebSocket -from pytest_simcore.helpers.playwright import web_socket_default_log_handler +from playwright.sync_api import Page +from pytest_simcore.helpers.playwright import RestartableWebSocket from pytest_simcore.helpers.playwright_sim4life import ( check_video_streaming, interact_with_s4l, @@ -22,7 +22,7 @@ def test_template( page: Page, create_project_from_template_dashboard: Callable[[str], dict[str, Any]], - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, template_id: str, is_autoscaled: bool, check_videostreaming: bool, @@ -40,9 +40,9 @@ def test_template( page, node_ids[0], log_in_and_out, autoscaled=is_autoscaled, copy_workspace=True ) s4l_websocket = resp["websocket"] - with web_socket_default_log_handler(s4l_websocket): - s4l_iframe = resp["iframe"] - interact_with_s4l(page, s4l_iframe) + # with web_socket_default_log_handler(s4l_websocket): + s4l_iframe = resp["iframe"] + interact_with_s4l(page, s4l_iframe) - if check_videostreaming: - check_video_streaming(page, s4l_iframe, s4l_websocket) + if check_videostreaming: + check_video_streaming(page, s4l_iframe, s4l_websocket) diff --git a/tests/e2e-playwright/tests/sleepers/test_sleepers.py b/tests/e2e-playwright/tests/sleepers/test_sleepers.py index d12953d1ed0..4415fcf3e49 100644 --- a/tests/e2e-playwright/tests/sleepers/test_sleepers.py +++ b/tests/e2e-playwright/tests/sleepers/test_sleepers.py @@ -16,7 +16,7 @@ from packaging.version import Version from packaging.version import parse as parse_version -from playwright.sync_api import Page, WebSocket +from playwright.sync_api import Page from pytest_simcore.helpers.logging_tools import ( ContextMessages, log_context, @@ -24,6 +24,7 @@ ) from pytest_simcore.helpers.playwright import ( MINUTE, + RestartableWebSocket, RunningState, ServiceType, SocketIOEvent, @@ -78,7 +79,7 @@ def _get_file_names(page: Page) -> list[str]: def test_sleepers( page: Page, - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, create_project_from_service_dashboard: Callable[ [ServiceType, str, str | None], dict[str, Any] ], diff --git a/tests/e2e-playwright/tests/tip/test_ti_plan.py b/tests/e2e-playwright/tests/tip/test_ti_plan.py index 56f028d197d..7709071e7ea 100644 --- a/tests/e2e-playwright/tests/tip/test_ti_plan.py +++ b/tests/e2e-playwright/tests/tip/test_ti_plan.py @@ -19,6 +19,7 @@ from pytest_simcore.helpers.playwright import ( MINUTE, SECOND, + RestartableWebSocket, app_mode_trigger_next_app, expected_service_running, wait_for_service_running, @@ -89,7 +90,7 @@ def __call__(self, message: str) -> bool: def test_classic_ti_plan( # noqa: PLR0915 page: Page, - log_in_and_out: WebSocket, + log_in_and_out: RestartableWebSocket, is_autoscaled: bool, is_product_lite: bool, create_tip_plan_from_dashboard: Callable[[str], dict[str, Any]], @@ -209,11 +210,12 @@ def test_classic_ti_plan( # noqa: PLR0915 ti_iframe = service_running.iframe_locator assert ti_iframe - jlab_websocket = ws_info.value + assert not ws_info.value.is_closed() + restartable_jlab_websocket = RestartableWebSocket.create(page, ws_info.value) with ( log_context(logging.INFO, "Run optimization"), - jlab_websocket.expect_event( + restartable_jlab_websocket.expect_event( "framereceived", _JLabWebSocketWaiter( expected_header_msg_type="stream", @@ -228,11 +230,18 @@ def test_classic_ti_plan( # noqa: PLR0915 ) with log_context(logging.INFO, "Create report"): - - ti_iframe.get_by_role("button", name="Load Analysis").click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) - ti_iframe.get_by_role("button", name="Load").nth(1).click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Load Analysis` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Load Analysis").click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Load` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Load").nth(1).click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) if is_product_lite: assert ( @@ -248,14 +257,34 @@ def test_classic_ti_plan( # noqa: PLR0915 ).is_enabled() else: - ti_iframe.get_by_role("button", name="Add to Report (0)").nth(0).click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) - ti_iframe.get_by_role("button", name="Export to S4L").click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) - ti_iframe.get_by_role("button", name="Add to Report (1)").nth(1).click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) - ti_iframe.get_by_role("button", name="Export Report").click() - page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Add to Report (0)` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Add to Report (0)").nth( + 0 + ).click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Export to S4L` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Export to S4L").click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Add to Report (1)` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Add to Report (1)").nth( + 1 + ).click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) + with log_context( + logging.INFO, + f"Click button - `Export Report` and wait for {_JLAB_REPORTING_MAX_TIME}", + ): + ti_iframe.get_by_role("button", name="Export Report").click() + page.wait_for_timeout(_JLAB_REPORTING_MAX_TIME) with log_context(logging.INFO, "Check outputs"): if is_product_lite: From 88b8f723f11e7dac394346c5d131e2f421e7ac58 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 11:11:42 +0100 Subject: [PATCH 2/9] improving e2e tests --- tests/e2e-playwright/Makefile | 13 -- .../tests/osparc_platform/conftest.py | 0 .../tests/osparc_platform/test_platform.py | 135 ------------------ 3 files changed, 148 deletions(-) delete mode 100644 tests/e2e-playwright/tests/osparc_platform/conftest.py delete mode 100644 tests/e2e-playwright/tests/osparc_platform/test_platform.py diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 55b46c4c0b4..8fcd6a54b9a 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -107,19 +107,6 @@ test-sleepers-dev: _check_venv_active ## runs sleepers test on local deploy $(CURDIR)/tests/sleepers/test_sleepers.py -.PHONY: test-osparc-platform-dev -test-osparc-platform-dev: _check_venv_active - @PWDEBUG=1 pytest \ - -sxvv \ - --color=yes \ - --pdb \ - --product-url=http://$(get_my_ip):9081 \ - --headed \ - --autoregister \ - --tracing=on \ - $(CURDIR)/tests/osparc_platform/test_platform.py - - # Define the files where user input will be saved SLEEPERS_INPUT_FILE := .e2e-playwright-sleepers-env.txt S4L_INPUT_FILE := .e2e-playwright-sim4life-env.txt diff --git a/tests/e2e-playwright/tests/osparc_platform/conftest.py b/tests/e2e-playwright/tests/osparc_platform/conftest.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/e2e-playwright/tests/osparc_platform/test_platform.py b/tests/e2e-playwright/tests/osparc_platform/test_platform.py deleted file mode 100644 index a5fad87df00..00000000000 --- a/tests/e2e-playwright/tests/osparc_platform/test_platform.py +++ /dev/null @@ -1,135 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -# pylint: disable=unused-variable -# pylint: disable=too-many-arguments -# pylint: disable=too-many-statements -# pylint: disable=no-name-in-module - -# import logging -# import re -# from collections.abc import Callable, Iterator - -# import pytest -# from playwright.sync_api import BrowserContext, Page, WebSocket -# from pydantic import AnyUrl -# from pytest_simcore.helpers.logging_tools import log_context -# from pytest_simcore.helpers.playwright import ( -# AutoRegisteredUser, -# web_socket_default_log_handler, -# ) -# from pytest_simcore.helpers.pydantic_extension import Secret4TestsStr - - -# @pytest.fixture -# def log_in_and_out( -# page: Page, -# product_url: AnyUrl, -# user_name: str, -# user_password: Secret4TestsStr, -# auto_register: bool, -# register: Callable[[], AutoRegisteredUser], -# context: BrowserContext, -# ) -> Iterator[WebSocket]: -# with log_context( -# logging.INFO, -# f"Open {product_url=} using {user_name=}/{user_password=}/{auto_register=}", -# ): -# response = page.goto(f"{product_url}") -# assert response -# assert response.ok, response.body() - -# # In case the accept cookies or new release window shows up, we accept -# page.wait_for_timeout(2000) -# acceptCookiesBtnLocator = page.get_by_test_id("acceptCookiesBtn") -# if acceptCookiesBtnLocator.is_visible(): -# acceptCookiesBtnLocator.click() -# page.wait_for_timeout(1000) -# newReleaseCloseBtnLocator = page.get_by_test_id("newReleaseCloseBtn") -# if newReleaseCloseBtnLocator.is_visible(): -# newReleaseCloseBtnLocator.click() - -# with ( -# log_context( -# logging.INFO, -# f"Log in {product_url} using {user_name=}/{user_password=}/{auto_register=}", -# ), -# page.expect_websocket() as ws_info, -# ): -# if auto_register: -# register() -# else: -# with log_context( -# logging.INFO, -# f"Log in {product_url=} using {user_name=}/{user_password=}", -# ): -# _user_email_box = page.get_by_test_id("loginUserEmailFld") -# _user_email_box.click() -# _user_email_box.fill(user_name) -# _user_password_box = page.get_by_test_id("loginPasswordFld") -# _user_password_box.click() -# _user_password_box.fill(user_password.get_secret_value()) -# with page.expect_response(re.compile(r"/login")) as response_info: -# page.get_by_test_id("loginSubmitBtn").click() -# assert response_info.value.ok, f"{response_info.value.json()}" - -# assert not ws_info.value.is_closed() - - -# # Welcome to Sim4Life -# page.wait_for_timeout(5000) -# welcomeToSim4LifeLocator = page.get_by_text("Welcome to Sim4Life") -# if welcomeToSim4LifeLocator.is_visible(): -# page.get_by_text("").nth( -# 1 -# ).click() # There is missing osparc-test-id for this button -# # Quick start window -# quickStartWindowCloseBtnLocator = page.get_by_test_id("quickStartWindowCloseBtn") -# if quickStartWindowCloseBtnLocator.is_visible(): -# quickStartWindowCloseBtnLocator.click() - -# context.storage_state(path="state.json") - -# yield ws - -# with log_context( -# logging.INFO, -# f"Log out of {product_url=} using {user_name=}/{user_password=}", -# ): -# page.keyboard.press("Escape") -# page.get_by_test_id("userMenuBtn").click() -# with page.expect_response(re.compile(r"/auth/logout")) as response_info: -# page.get_by_test_id("userMenuLogoutBtn").click() -# assert response_info.value.ok, f"{response_info.value.json()}" -# # so we see the logout page -# page.wait_for_timeout(500) - - -# @pytest.fixture -# def logged_in_context(playwright): -# browser = playwright.chromium.launch(headless=False) -# context = browser.new_context(storage_state="state.json") -# yield context -# context.close() -# browser.close() - - -# def test_simple_folder_workflow(logged_in_context, product_url): -# page = logged_in_context.new_page() - -# page.goto(f"{product_url}") -# page.wait_for_timeout(1000) -# page.get_by_test_id("dashboard").get_by_text("New folder", exact=True).click() -# page.get_by_placeholder("Title").fill("My new folder") -# page.get_by_placeholder("Title").press("Enter") - -# page.get_by_test_id("dashboard").get_by_text("My new folder").click() -# page.get_by_test_id("contextTree").get_by_text("My Workspace").click() - - -# def test_simple_workspace_workflow(logged_in_context, product_url): -# page = logged_in_context.new_page() - -# page.goto(f"{product_url}") -# page.wait_for_timeout(1000) -# page.get_by_test_id("userMenuBtn").click() -# page.wait_for_timeout(1000) From 982c2bd83c745e6f8a5b9799faffff556d7ebd64 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 11:12:38 +0100 Subject: [PATCH 3/9] improving e2e tests --- tests/e2e-playwright/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 8fcd6a54b9a..88a15a845d1 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -172,7 +172,7 @@ define run_test pytest -s $2 \ --color=yes \ --browser chromium \ - --headed \ + --headed \ $$TEST_ARGS endef From 0c7861be8fdc0974a74a75eba6dd6b47c546cfd0 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 11:20:21 +0100 Subject: [PATCH 4/9] improving e2e tests --- tests/e2e-playwright/tests/sim4life/test_template.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/e2e-playwright/tests/sim4life/test_template.py b/tests/e2e-playwright/tests/sim4life/test_template.py index d4200611ab4..423437e04a4 100644 --- a/tests/e2e-playwright/tests/sim4life/test_template.py +++ b/tests/e2e-playwright/tests/sim4life/test_template.py @@ -11,6 +11,7 @@ from typing import Any from playwright.sync_api import Page +from pydantic import AnyUrl from pytest_simcore.helpers.playwright import RestartableWebSocket from pytest_simcore.helpers.playwright_sim4life import ( check_video_streaming, @@ -26,6 +27,7 @@ def test_template( template_id: str, is_autoscaled: bool, check_videostreaming: bool, + product_url: AnyUrl, ): project_data = create_project_from_template_dashboard(template_id) @@ -37,10 +39,14 @@ def test_template( assert len(node_ids) == 1, "Expected 1 node in the workbench!" resp = wait_for_launched_s4l( - page, node_ids[0], log_in_and_out, autoscaled=is_autoscaled, copy_workspace=True + page, + node_ids[0], + log_in_and_out, + autoscaled=is_autoscaled, + copy_workspace=True, + product_url=product_url, ) s4l_websocket = resp["websocket"] - # with web_socket_default_log_handler(s4l_websocket): s4l_iframe = resp["iframe"] interact_with_s4l(page, s4l_iframe) From 49b9c8601f88ad440346925a29566fc278a2da42 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 14:19:44 +0100 Subject: [PATCH 5/9] introducing CI playwright tests --- ci/github/system-testing/e2e-playwright.bash | 1 + tests/e2e-playwright/Makefile | 23 +++++++ tests/e2e-playwright/tests/conftest.py | 10 +++ .../tests/platform_CI_tests/conftest.py | 0 .../tests/platform_CI_tests/test_platform.py | 63 +++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 tests/e2e-playwright/tests/platform_CI_tests/conftest.py create mode 100644 tests/e2e-playwright/tests/platform_CI_tests/test_platform.py diff --git a/ci/github/system-testing/e2e-playwright.bash b/ci/github/system-testing/e2e-playwright.bash index 88d45bd86a4..b0a6e498f66 100755 --- a/ci/github/system-testing/e2e-playwright.bash +++ b/ci/github/system-testing/e2e-playwright.bash @@ -24,6 +24,7 @@ test() { source .venv/bin/activate pushd tests/e2e-playwright make test-sleepers + make test-platform popd } diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index fc4c0463de2..2ab3ab27447 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -106,6 +106,29 @@ test-sleepers-dev: _check_venv_active ## runs sleepers test on local deploy --autoregister \ $(CURDIR)/tests/sleepers/test_sleepers.py +.PHONY: test-platform +test-platform: _check_venv_active + @pytest \ + -sxvv \ + --color=yes \ + --pdb \ + --product-url=http://$(get_my_ip):9081 \ + --autoregister \ + --tracing=on \ + $(CURDIR)/tests/platform_CI_tests/test_platform.py + +.PHONY: test-platform-dev +test-platform-dev: _check_venv_active ## runs platform test on local deploy (with PWDEBUG=1) + @PWDEBUG=1 pytest \ + -sxvv \ + --color=yes \ + --pdb \ + --product-url=http://$(get_my_ip):9081 \ + --headed \ + --autoregister \ + --tracing=on \ + $(CURDIR)/tests/platform_CI_tests/test_platform.py + # Define the files where user input will be saved SLEEPERS_INPUT_FILE := .e2e-playwright-sleepers-env.txt diff --git a/tests/e2e-playwright/tests/conftest.py b/tests/e2e-playwright/tests/conftest.py index 551dc1ad266..665ba78d774 100644 --- a/tests/e2e-playwright/tests/conftest.py +++ b/tests/e2e-playwright/tests/conftest.py @@ -323,6 +323,11 @@ def _do() -> AutoRegisteredUser: return _do +@pytest.fixture(scope="session") +def store_browser_context() -> bool: + return False + + @pytest.fixture def log_in_and_out( page: Page, @@ -331,6 +336,8 @@ def log_in_and_out( user_password: Secret4TestsStr, auto_register: bool, register: Callable[[], AutoRegisteredUser], + store_browser_context: bool, + context: BrowserContext, ) -> Iterator[RestartableWebSocket]: with log_context( logging.INFO, @@ -389,6 +396,9 @@ def log_in_and_out( if quickStartWindowCloseBtnLocator.is_visible(): quickStartWindowCloseBtnLocator.click() + if store_browser_context: + context.storage_state(path="state.json") + # with web_socket_default_log_handler(ws): yield restartable_wb diff --git a/tests/e2e-playwright/tests/platform_CI_tests/conftest.py b/tests/e2e-playwright/tests/platform_CI_tests/conftest.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py new file mode 100644 index 00000000000..bd6d43e8899 --- /dev/null +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -0,0 +1,63 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments +# pylint: disable=too-many-statements +# pylint: disable=no-name-in-module + +from pathlib import Path + +import pytest + + +@pytest.fixture(scope="session") +def store_browser_context() -> bool: + return True + + +@pytest.fixture +def logged_in_context( + playwright, store_browser_context: bool, request: pytest.FixtureRequest +): + file_path = Path("state.json") + if not file_path.exists(): + request.getfixturevalue("log_in_and_out") + + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(storage_state="state.json") + yield context + context.close() + browser.close() + + +@pytest.fixture(scope="module") +def test_module_teardown(): + + yield # Run the tests + + file_path = Path("state.json") + if file_path.exists(): + file_path.unlink() + + +def test_simple_folder_workflow(logged_in_context, product_url, test_module_teardown): + page = logged_in_context.new_page() + + page.goto(f"{product_url}") + page.wait_for_timeout(1000) + page.get_by_test_id("dashboard").get_by_text("New folder", exact=True).click() + page.get_by_placeholder("Title").fill("My new folder") + page.get_by_placeholder("Title").press("Enter") + + page.get_by_test_id("dashboard").get_by_text("My new folder").click() + page.get_by_test_id("contextTree").get_by_text("My Workspace").click() + + +def test_simple_workspace_workflow( + logged_in_context, product_url, test_module_teardown +): + page = logged_in_context.new_page() + + page.goto(f"{product_url}") + page.wait_for_timeout(1000) + page.get_by_text("Shared Workspaces").click() From 41407602bad08daf91066ca32cf9cb6c581866a2 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 14:59:58 +0100 Subject: [PATCH 6/9] fix headed mode --- tests/e2e-playwright/Makefile | 2 +- .../tests/platform_CI_tests/test_platform.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 2ab3ab27447..6cbff2c68ec 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -66,7 +66,7 @@ RETRY_INTERVAL_SECONDS := 1 install-ci-up-simcore: install-ci @$(MAKE_C) $(REPO_BASE_DIR) local-registry - @$(_transfer-images-to-registry) + @$(_transfer-to-registry) @$(_up_simcore) @$(VENV_DIR)/bin/python utils/wait_for_services.py diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index bd6d43e8899..c3228a694b5 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -17,13 +17,18 @@ def store_browser_context() -> bool: @pytest.fixture def logged_in_context( - playwright, store_browser_context: bool, request: pytest.FixtureRequest + playwright, + store_browser_context: bool, + request: pytest.FixtureRequest, + pytestconfig, ): + is_headed = "--headed" in pytestconfig.invocation_params.args + file_path = Path("state.json") if not file_path.exists(): request.getfixturevalue("log_in_and_out") - browser = playwright.chromium.launch(headless=False) + browser = playwright.chromium.launch(headless=not is_headed) context = browser.new_context(storage_state="state.json") yield context context.close() From 774bbe9bd6b88a9d3c38720d958a60f82c639a92 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Tue, 19 Nov 2024 15:03:46 +0100 Subject: [PATCH 7/9] fix --- tests/e2e-playwright/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 6cbff2c68ec..2ab3ab27447 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -66,7 +66,7 @@ RETRY_INTERVAL_SECONDS := 1 install-ci-up-simcore: install-ci @$(MAKE_C) $(REPO_BASE_DIR) local-registry - @$(_transfer-to-registry) + @$(_transfer-images-to-registry) @$(_up_simcore) @$(VENV_DIR)/bin/python utils/wait_for_services.py From 757f4c4f3844e879de49f151164c4abfb65bcec4 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 20 Nov 2024 10:53:27 +0100 Subject: [PATCH 8/9] improving e2e tests --- tests/e2e-playwright/Makefile | 6 ++-- .../tests/platform_CI_tests/test_platform.py | 30 +++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/tests/e2e-playwright/Makefile b/tests/e2e-playwright/Makefile index 2ab3ab27447..40fe914b9c0 100644 --- a/tests/e2e-playwright/Makefile +++ b/tests/e2e-playwright/Makefile @@ -64,7 +64,8 @@ install-dev install-prod install-ci: _check_venv_active ## install app in develo RETRY_DURATION_SECONDS := 30 RETRY_INTERVAL_SECONDS := 1 -install-ci-up-simcore: install-ci +.PHONY: install-ci-up-simcore +install-ci-up-simcore: install-ci ## run registry and simcore stack locally (push sleepers image and modifies DB) @$(MAKE_C) $(REPO_BASE_DIR) local-registry @$(_transfer-images-to-registry) @$(_up_simcore) @@ -107,11 +108,10 @@ test-sleepers-dev: _check_venv_active ## runs sleepers test on local deploy $(CURDIR)/tests/sleepers/test_sleepers.py .PHONY: test-platform -test-platform: _check_venv_active +test-platform: _check_venv_active ## runs platform test on local deploy @pytest \ -sxvv \ --color=yes \ - --pdb \ --product-url=http://$(get_my_ip):9081 \ --autoregister \ --tracing=on \ diff --git a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py index c3228a694b5..edcac0fca64 100644 --- a/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py +++ b/tests/e2e-playwright/tests/platform_CI_tests/test_platform.py @@ -50,12 +50,19 @@ def test_simple_folder_workflow(logged_in_context, product_url, test_module_tear page.goto(f"{product_url}") page.wait_for_timeout(1000) - page.get_by_test_id("dashboard").get_by_text("New folder", exact=True).click() - page.get_by_placeholder("Title").fill("My new folder") - page.get_by_placeholder("Title").press("Enter") + page.get_by_test_id("newFolderButton").click() - page.get_by_test_id("dashboard").get_by_text("My new folder").click() - page.get_by_test_id("contextTree").get_by_text("My Workspace").click() + with page.expect_response( + lambda response: "folders" in response.url + and response.status == 201 + and response.request.method == "POST" + ) as response_info: + page.get_by_test_id("folderEditorTitle").fill("My new folder") + page.get_by_test_id("folderEditorCreate").click() + + _folder_id = response_info.value.json()["data"]["folderId"] + page.get_by_test_id(f"folderItem_{_folder_id}").click() + page.get_by_test_id("workspacesAndFoldersTreeItem_null_null").click() def test_simple_workspace_workflow( @@ -65,4 +72,15 @@ def test_simple_workspace_workflow( page.goto(f"{product_url}") page.wait_for_timeout(1000) - page.get_by_text("Shared Workspaces").click() + page.get_by_test_id("workspacesAndFoldersTreeItem_-1_null").click() + + with page.expect_response( + lambda response: "workspaces" in response.url + and response.status == 201 + and response.request.method == "POST" + ) as response_info: + page.get_by_test_id("newWorkspaceButton").click() + page.get_by_test_id("workspaceEditorSave").click() + _workspace_id = response_info.value.json()["data"]["workspaceId"] + page.get_by_test_id(f"workspaceItem_{_workspace_id}").click() + page.get_by_test_id("workspacesAndFoldersTreeItem_null_null").click() From db6a7ed65c6887596b605ab6ecf8ae5d5b69b1d6 Mon Sep 17 00:00:00 2001 From: matusdrobuliak66 Date: Wed, 20 Nov 2024 11:04:32 +0100 Subject: [PATCH 9/9] final touches --- .../src/pytest_simcore/helpers/playwright.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py index 02b8c457385..22ea24baad0 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py @@ -339,9 +339,14 @@ def __call__(self, message: str) -> bool: url = f"https://{self.node_id}.services.{self.get_partial_product_url()}" response = httpx.get(url, timeout=10) self.logger.info( - "Querying the service endpoint from the E2E test. Url: %s Response: %s", + "Querying the service endpoint from the E2E test. Url: %s Response: %s TIP: %s", url, response, + ( + "Response 401 is OK. It means that service is ready." + if response.status_code == 401 + else "We are emulating the frontend; a 500 response is acceptable if the service is not yet ready." + ), ) if response.status_code <= 401: # 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)