Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✅ e2e: new workflow to test tiplite #6388

Merged
merged 22 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pydantic import SecretStr


def _mask(value):
"""
Mask the password, showing only the first and last characters
or *** if very short passwords
"""
if len(value) > 2:
masked_value = value[0] + "*" * (len(value) - 2) + value[-1]
else:
# In case of very short passwords
masked_value = "*" * len(value)
return masked_value


def _hash(value):
"""Uses hash number to mask the password"""
return f"hash:{hash(value)}"


class Secret4TestsStr(SecretStr):
"""Prints a hint of the secret
TIP: Can be handy for testing
"""

def _display(self) -> str | bytes:
# SEE overrides _SecretBase._display
value = self.get_secret_value()
return _mask(value) if value else ""


assert str(Secret4TestsStr("123456890")) == "1*******0"
assert "1*******0" in repr(Secret4TestsStr("123456890"))
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ qx.Class.define("osparc.product.TIPTeaser", {
});

this.getChildControl("teaser-text");

osparc.utils.Utils.setIdToWidget(this, "tipTeaserWindow");

const closeBtn = this.getChildControl("close-button");
osparc.utils.Utils.setIdToWidget(closeBtn, "tipTeaserWindowCloseBtn");
},

statics: {
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e-playwright/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
test-results
.e2e-playwright-*.txt
assets
report.html
.e2e-playwright-*.txt
report.xml
test-results
6 changes: 5 additions & 1 deletion tests/e2e-playwright/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,17 @@ CLASSIC_TIP_INPUT_FILE := .e2e-playwright-classictip-env.txt
$(SLEEPERS_INPUT_FILE) $(JUPYTER_LAB_INPUT_FILE) $(CLASSIC_TIP_INPUT_FILE) $(S4L_INPUT_FILE):
@read -p "Enter your product URL: " PRODUCT_URL; \
read -p "Is the product billable [y/n]: " BILLABLE; \
read -p "Is the product lite [y/n]: " IS_LITE; \
read -p "Is the test running in autoscaled deployment [y/n]: " AUTOSCALED; \
read -p "Enter your username: " USER_NAME; \
read -s -p "Enter your password: " PASSWORD; echo ""; \
echo "--product-url=$$PRODUCT_URL --user-name=$$USER_NAME --password=$$PASSWORD" > $@; \
if [ "$$BILLABLE" = "y" ]; then \
echo "--product-billable" >> $@; \
fi; \
if [ "$$IS_LITE" = "y" ]; then \
echo "--product-lite" >> $@; \
pcrespov marked this conversation as resolved.
Show resolved Hide resolved
fi; \
if [ "$$AUTOSCALED" = "y" ]; then \
echo "--autoscaled" >> $@; \
fi; \
Expand Down Expand Up @@ -183,4 +187,4 @@ define run_test_on_chrome
endef

clean:
@rm -rf $(SLEEPERS_INPUT_FILE) $(JUPYTER_LAB_INPUT_FILE) $(CLASSIC_TIP_INPUT_FILE)
-@rm -rf $(SLEEPERS_INPUT_FILE) $(JUPYTER_LAB_INPUT_FILE) $(CLASSIC_TIP_INPUT_FILE)
28 changes: 21 additions & 7 deletions tests/e2e-playwright/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@


## Usage

### Auto generate new test
`playwright codegen sim4life.io`
```
playwright codegen sim4life.io
```

### Run test locally with headed mode
```
pytest -s tests/sim4life.py --headed --browser chromium --product-billable --product-url https://sim4life.io/ --user-name YOUR_USERNAME --password YOUR_PASSWORD --service-key sim4life-8-0-0-dy
```

### Check test results output
`playwright show-trace test-results/tests-sim4life-py-test-billable-sim4life-chromium/trace.zip`
```
playwright show-trace test-results/tests-sim4life-py-test-billable-sim4life-chromium/trace.zip
```

### Run debug mode
`PWDEBUG=1 pytest -s tests/sim4life.py`
```
PWDEBUG=1 pytest -s tests/sim4life.py
```

### Run test in different browsers
`pytest -s tests/sim4life.py --tracing on --html=report.html --browser chromium --browser firefox`
```
pytest -s tests/sim4life.py --tracing on --html=report.html --browser chromium --browser firefox
```

### or in chrome/msedge
`pytest -s tests/sim4life.py --tracing on --html=report.html --browser-channel chrome`
### or in chrome/ms-edge
```
pytest -s tests/sim4life.py --tracing on --html=report.html --browser-channel chrome
```

### Runs in CI
## e2e CI
- https://git.speag.com/oSparc/e2e-backend
52 changes: 33 additions & 19 deletions tests/e2e-playwright/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from playwright.sync_api import APIRequestContext, BrowserContext, Page, WebSocket
from playwright.sync_api._generated import Playwright
from pydantic import AnyUrl, TypeAdapter
from pytest import Item
from pytest_simcore.helpers.faker_factories import DEFAULT_TEST_PASSWORD
from pytest_simcore.helpers.logging_tools import log_context
from pytest_simcore.helpers.playwright import (
MINUTE,
Expand All @@ -36,6 +36,7 @@
decode_socketio_42_message,
web_socket_default_log_handler,
)
from pytest_simcore.helpers.pydantic_extension import Secret4TestsStr

_PROJECT_CLOSING_TIMEOUT: Final[int] = 10 * MINUTE
_OPENING_NEW_EMPTY_PROJECT_MAX_WAIT_TIME: Final[int] = 30 * SECOND
Expand Down Expand Up @@ -79,6 +80,12 @@ def pytest_addoption(parser: pytest.Parser) -> None:
default=False,
help="Whether product is billable or not",
)
group.addoption(
"--product-lite",
action="store_true",
default=False,
help="Whether product is lite version or not",
)
group.addoption(
"--autoscaled",
action="store_true",
Expand Down Expand Up @@ -116,7 +123,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:


# Dictionary to store start times of tests
_test_start_times = {}
_test_start_times: dict[str, datetime.datetime] = {}


def pytest_runtest_setup(item):
Expand Down Expand Up @@ -144,7 +151,7 @@ def _construct_graylog_url(
return f"{monitoring_url}/graylog/search?{query}"


def pytest_runtest_makereport(item: Item, call):
def pytest_runtest_makereport(item: pytest.Item, call):
"""
Hook to add extra information when a test fails.
"""
Expand All @@ -171,7 +178,6 @@ def pytest_runtest_makereport(item: Item, call):
)
diagnostics["duration"] = str(end_time - start_time)

# Print the diagnostics report
with log_context(
logging.WARNING,
f"ℹ️ Diagnostics report for {test_name} ---", # noqa: RUF001
Expand Down Expand Up @@ -217,23 +223,29 @@ def user_name(request: pytest.FixtureRequest, auto_register: bool, faker: Faker)
@pytest.fixture
def user_password(
request: pytest.FixtureRequest, auto_register: bool, faker: Faker
) -> str:
) -> Secret4TestsStr:
if auto_register:
return faker.password(length=12)
return Secret4TestsStr(DEFAULT_TEST_PASSWORD)
if osparc_password := request.config.getoption("--password"):
assert isinstance(osparc_password, str)
return osparc_password
return os.environ["USER_PASSWORD"]
return Secret4TestsStr(osparc_password)
return Secret4TestsStr(os.environ["USER_PASSWORD"])


@pytest.fixture(scope="session")
def product_billable(request: pytest.FixtureRequest) -> bool:
def is_product_billable(request: pytest.FixtureRequest) -> bool:
billable = request.config.getoption("--product-billable")
return TypeAdapter(bool).validate_python(billable)


@pytest.fixture(scope="session")
def autoscaled(request: pytest.FixtureRequest) -> bool:
def is_product_lite(request: pytest.FixtureRequest) -> bool:
enabled = request.config.getoption("--product-lite")
return TypeAdapter(bool).validate_python(enabled)


@pytest.fixture(scope="session")
def is_autoscaled(request: pytest.FixtureRequest) -> bool:
autoscaled = request.config.getoption("--autoscaled")
return TypeAdapter(bool).validate_python(autoscaled)

Expand Down Expand Up @@ -280,7 +292,7 @@ def register(
page: Page,
product_url: AnyUrl,
user_name: str,
user_password: str,
user_password: Secret4TestsStr,
) -> Callable[[], AutoRegisteredUser]:
def _do() -> AutoRegisteredUser:
with log_context(
Expand All @@ -297,11 +309,13 @@ def _do() -> AutoRegisteredUser:
for pass_id in ["registrationPass1Fld", "registrationPass2Fld"]:
user_password_box = page.get_by_test_id(pass_id)
user_password_box.click()
user_password_box.fill(user_password)
user_password_box.fill(user_password.get_secret_value())
with page.expect_response(re.compile(r"/auth/register")) as response_info:
page.get_by_test_id("registrationSubmitBtn").click()
assert response_info.value.ok, response_info.value.json()
return AutoRegisteredUser(user_email=user_name, password=user_password)
return AutoRegisteredUser(
user_email=user_name, password=user_password.get_secret_value()
)

return _do

Expand All @@ -311,7 +325,7 @@ def log_in_and_out(
page: Page,
product_url: AnyUrl,
user_name: str,
user_password: str,
user_password: Secret4TestsStr,
auto_register: bool,
register: Callable[[], AutoRegisteredUser],
) -> Iterator[WebSocket]:
Expand Down Expand Up @@ -352,7 +366,7 @@ def log_in_and_out(
_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)
_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()}"
Expand Down Expand Up @@ -392,7 +406,7 @@ def log_in_and_out(
def create_new_project_and_delete(
page: Page,
log_in_and_out: WebSocket,
product_billable: bool,
is_product_billable: bool,
api_request_context: APIRequestContext,
product_url: AnyUrl,
) -> Iterator[Callable[[tuple[RunningState], bool], dict[str, Any]]]:
Expand All @@ -411,7 +425,7 @@ def _(
), "misuse of this fixture! only 1 study can be opened at a time. Otherwise please modify the fixture"
with log_context(
logging.INFO,
f"Open project in {product_url=} as {product_billable=}",
f"Open project in {product_url=} as {is_product_billable=}",
) as ctx:
waiter = SocketIOProjectStateUpdatedWaiter(expected_states=expected_states)
timeout = (
Expand Down Expand Up @@ -473,7 +487,7 @@ def wait_for_done(response):
...
else:
open_button.click()
if product_billable:
if is_product_billable:
# Open project with default resources
page.get_by_test_id("openWithResources").click()
project_data = response_info.value.json()
Expand Down Expand Up @@ -512,7 +526,7 @@ def wait_for_done(response):
for project_uuid in created_project_uuids:
with log_context(
logging.INFO,
f"Delete project with {project_uuid=} in {product_url=} as {product_billable=}",
f"Delete project with {project_uuid=} in {product_url=} as {is_product_billable=}",
):
response = api_request_context.delete(
f"{product_url}v0/projects/{project_uuid}"
Expand Down
8 changes: 6 additions & 2 deletions tests/e2e-playwright/tests/sim4life/test_sim4life.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_sim4life(
log_in_and_out: WebSocket,
service_key: str,
use_plus_button: bool,
autoscaled: bool,
is_autoscaled: bool,
check_videostreaming: bool,
):
if use_plus_button:
Expand All @@ -49,7 +49,11 @@ def test_sim4life(
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=autoscaled, copy_workspace=False
page,
node_ids[0],
log_in_and_out,
autoscaled=is_autoscaled,
copy_workspace=False,
)
s4l_websocket = resp["websocket"]
with web_socket_default_log_handler(s4l_websocket):
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e-playwright/tests/sim4life/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_template(
create_project_from_template_dashboard: Callable[[str], dict[str, Any]],
log_in_and_out: WebSocket,
template_id: str,
autoscaled: bool,
is_autoscaled: bool,
check_videostreaming: bool,
):
project_data = create_project_from_template_dashboard(template_id)
Expand All @@ -37,7 +37,7 @@ 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=autoscaled, copy_workspace=True
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):
Expand Down
Loading
Loading