Skip to content

Commit 7e757c5

Browse files
viniciusdcdcmcand
andauthored
Fix Playwright CI errors & update local instructions (#2943)
Co-authored-by: Chuck McAndrew <6248903+dcmcand@users.noreply.github.com>
1 parent db424c3 commit 7e757c5

File tree

8 files changed

+134
-42
lines changed

8 files changed

+134
-42
lines changed

tests/common/handlers.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,31 @@ def _dismiss_kernel_popup(self):
8686
def _shutdown_all_kernels(self):
8787
"""Shutdown all running kernels."""
8888
logger.debug(">>> Shutting down all kernels")
89-
kernel_menu = self.page.get_by_role("menuitem", name="Kernel")
90-
kernel_menu.click()
89+
90+
# Open the "Kernel" menu
91+
self.page.get_by_role("menuitem", name="Kernel").click()
92+
93+
# Locate the "Shut Down All Kernels…" menu item
9194
shut_down_all = self.page.get_by_role("menuitem", name="Shut Down All Kernels…")
92-
logger.debug(
93-
f">>> Shut down all kernels visible: {shut_down_all.is_visible()} enabled: {shut_down_all.is_enabled()}"
94-
)
95-
if shut_down_all.is_visible() and shut_down_all.is_enabled():
96-
shut_down_all.click()
97-
self.page.get_by_role("button", name="Shut Down All").click()
98-
else:
95+
96+
# If it's not visible or is disabled, there's nothing to shut down
97+
if not shut_down_all.is_visible() or shut_down_all.is_disabled():
9998
logger.debug(">>> No kernels to shut down")
99+
return
100+
101+
# Otherwise, click to shut down all kernels and confirm
102+
shut_down_all.click()
103+
self.page.get_by_role("button", name="Shut Down All").click()
100104

101105
def _navigate_to_root_folder(self):
102106
"""Navigate back to the root folder in JupyterLab."""
107+
# Make sure the home directory is select in the sidebar
108+
if not self.page.get_by_role(
109+
"region", name="File Browser Section"
110+
).is_visible():
111+
file_browser_tab = self.page.get_by_role("tab", name="File Browser")
112+
file_browser_tab.click()
113+
103114
logger.debug(">>> Navigating to root folder")
104115
self.page.get_by_title(f"/home/{self.nav.username}", exact=True).locator(
105116
"path"
@@ -303,9 +314,18 @@ def _open_new_environment_tab(self):
303314
).to_be_visible()
304315

305316
def _assert_user_namespace(self):
306-
expect(
307-
self.page.get_by_role("button", name=f"{self.nav.username} Create a new")
308-
).to_be_visible()
317+
user_namespace_dropdown = self.page.get_by_role(
318+
"button", name=f"{self.nav.username} Create a new"
319+
)
320+
321+
if not (
322+
expect(
323+
user_namespace_dropdown
324+
).to_be_visible() # this asserts the user namespace shows in the UI
325+
or self.nav.username
326+
in user_namespace_dropdown.text_content() # this attests that the namespace corresponds to the logged in user
327+
):
328+
raise ValueError(f"User namespace {self.nav.username} not found")
309329

310330
def _get_shown_namespaces(self):
311331
_envs = self.page.locator("#environmentsScroll").get_by_role("button")

tests/common/navigator.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66

77
from playwright.sync_api import expect, sync_playwright
8+
from yarl import URL
89

910
logger = logging.getLogger()
1011

@@ -50,7 +51,7 @@ def setup(self):
5051
self.page = self.context.new_page()
5152
self.initialized = True
5253

53-
def _rename_test_video_path(self, video_path):
54+
def _rename_test_video_path(self, video_path: Path):
5455
"""Rename the test video file to the test unique identifier."""
5556
video_file_name = (
5657
f"{self.video_name_prefix}.mp4" if self.video_name_prefix else None
@@ -62,7 +63,7 @@ def teardown(self) -> None:
6263
"""Teardown Playwright browser and context."""
6364
if self.initialized:
6465
# Rename the video file to the test unique identifier
65-
current_video_path = self.page.video.path()
66+
current_video_path = Path(self.page.video.path())
6667
self._rename_test_video_path(current_video_path)
6768

6869
self.context.close()
@@ -87,10 +88,17 @@ class LoginNavigator(NavigatorMixin):
8788

8889
def __init__(self, nebari_url, username, password, auth="password", **kwargs):
8990
super().__init__(**kwargs)
90-
self.nebari_url = nebari_url
91+
self._nebari_url = URL(nebari_url)
9192
self.username = username
9293
self.password = password
9394
self.auth = auth
95+
logger.debug(
96+
f"LoginNavigator initialized with {self.auth} auth method. :: {self.nebari_url}"
97+
)
98+
99+
@property
100+
def nebari_url(self):
101+
return self._nebari_url.human_repr()
94102

95103
def login(self):
96104
"""Login to Nebari deployment using the provided authentication method."""
@@ -110,7 +118,7 @@ def logout(self):
110118

111119
def _login_google(self):
112120
logger.debug(">>> Sign in via Google and start the server")
113-
self.page.goto(self.nebari_url)
121+
self.page.goto(url=self.nebari_url)
114122
expect(self.page).to_have_url(re.compile(f"{self.nebari_url}*"))
115123

116124
self.page.get_by_role("button", name="Sign in with Keycloak").click()
@@ -123,7 +131,7 @@ def _login_google(self):
123131

124132
def _login_password(self):
125133
logger.debug(">>> Sign in via Username/Password")
126-
self.page.goto(self.nebari_url)
134+
self.page.goto(url=self.nebari_url)
127135
expect(self.page).to_have_url(re.compile(f"{self.nebari_url}*"))
128136

129137
self.page.get_by_role("button", name="Sign in with Keycloak").click()

tests/common/playwright_fixtures.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,43 @@ def load_env_vars():
2323
def build_params(request, pytestconfig, extra_params=None):
2424
"""Construct and return parameters for navigator instances."""
2525
env_vars = load_env_vars()
26+
27+
# Retrieve values from request or environment
28+
nebari_url = request.param.get("nebari_url") or env_vars.get("nebari_url")
29+
username = request.param.get("keycloak_username") or env_vars.get("username")
30+
password = request.param.get("keycloak_password") or env_vars.get("password")
31+
32+
# Validate that required fields are present
33+
if not nebari_url:
34+
raise ValueError(
35+
"Error: 'nebari_url' is required but was not provided in "
36+
"'request.param' or environment variables."
37+
)
38+
if not username:
39+
raise ValueError(
40+
"Error: 'username' is required but was not provided in "
41+
"'request.param' or environment variables."
42+
)
43+
if not password:
44+
raise ValueError(
45+
"Error: 'password' is required but was not provided in "
46+
"'request.param' or environment variables."
47+
)
48+
49+
# Build the params dictionary once all required fields are validated
2650
params = {
27-
"nebari_url": request.param.get("nebari_url") or env_vars["nebari_url"],
28-
"username": request.param.get("keycloak_username") or env_vars["username"],
29-
"password": request.param.get("keycloak_password") or env_vars["password"],
51+
"nebari_url": nebari_url,
52+
"username": username,
53+
"password": password,
3054
"auth": "password",
3155
"video_dir": "videos/",
3256
"headless": pytestconfig.getoption("--headed"),
3357
"slow_mo": pytestconfig.getoption("--slowmo"),
3458
}
59+
3560
if extra_params:
3661
params.update(extra_params)
62+
3763
return params
3864

3965

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
KEYCLOAK_USERNAME="USERNAME_OR_GOOGLE_EMAIL"
22
KEYCLOAK_PASSWORD="PASSWORD"
3-
NEBARI_FULL_URL="https://nebari.quansight.dev/"
3+
NEBARI_FULL_URL="https://localhost/"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.PHONY: setup
2+
3+
setup:
4+
@echo "Setting up correct pins for playwright user-journey tests"
5+
pip install -r requirements.txt
6+
@echo "Setting up playwright browser dependencies"
7+
playwright install
8+
@echo "Setting up .env file"
9+
cp .env.tpl .env
10+
@echo "Please fill in the .env file with the correct values"

tests/tests_e2e/playwright/README.md

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,48 +33,57 @@ tests
3333
- `handlers.py`: Contains classes fore handling the different level of access to
3434
services a User might encounter, such as Notebook, Conda-store and others.
3535

36-
## Setup
37-
38-
1. **Install Nebari with Development Requirements**
36+
Below is an example of how you might update the **Setup** and **Running the Playwright Tests** sections of your README to reflect the new `Makefile` and the updated `pytest` invocation.
3937

40-
Install Nebari including development requirements (which include Playwright):
38+
---
4139

42-
```bash
43-
pip install -e ".[dev]"
44-
```
40+
## Setup
4541

46-
2. **Install Playwright**
42+
1. **Use the provided Makefile to install dependencies**
4743

48-
Install Playwright:
44+
Navigate to the Playwright tests directory and run the `setup` target:
4945

5046
```bash
51-
playwright install
47+
cd tests_e2e/playwright
48+
make setup
5249
```
5350

54-
*Note:* If you see the warning `BEWARE: your OS is not officially supported by Playwright; downloading fallback build`, it is not critical. Playwright should still work (see microsoft/playwright#15124).
51+
This command will:
5552

56-
3. **Create Environment Vars**
53+
- Install the pinned dependencies from `requirements.txt`.
54+
- Install Playwright and its required browser dependencies.
55+
- Create a new `.env` file from `.env.tpl`.
5756

58-
Fill in your execution space environment with the following values:
57+
2. **Fill in the `.env` file**
5958

60-
- `KEYCLOAK_USERNAME`: Nebari username for username/password login or Google email address/Google sign-in.
61-
- `KEYCLOAK_PASSWORD`: Password associated with `KEYCLOAK_USERNAME`.
62-
- `NEBARI_FULL_URL`: Full URL path including scheme to the Nebari instance (e.g., "https://nebari.quansight.dev/").
59+
Open the newly created `.env` file and fill in the following values:
6360

64-
This user can be created with the following command (or use an existing non-root user):
61+
- `KEYCLOAK_USERNAME`: Nebari username for username/password login (or Google email for Google sign-in).
62+
- `KEYCLOAK_PASSWORD`: Password associated with the above username.
63+
- `NEBARI_FULL_URL`: Full URL (including `https://`) to the Nebari instance (e.g., `https://nebari.quansight.dev/`).
64+
65+
If you need to create a user for testing, you can do so with:
6566

6667
```bash
6768
nebari keycloak adduser --user <username> <password> --config <NEBARI_CONFIG_PATH>
6869
```
6970

70-
## Running the Playwright Tests
71+
*Note:* If you see the warning:
72+
```
73+
BEWARE: your OS is not officially supported by Playwright; downloading fallback build
74+
```
75+
it is not critical. Playwright should still work despite the warning.
7176

72-
Playwright tests are run inside of pytest using:
77+
## Running the Playwright Tests
7378

79+
You can run the Playwright tests with `pytest`.
7480
```bash
75-
pytest tests_e2e/playwright/test_playwright.py
81+
pytest tests_e2e/playwright/test_playwright.py --numprocesses auto
7682
```
7783

84+
> **Important**: Due to how Pytest manages async code; Playwright’s sync calls can conflict with default Pytest concurrency settings, and using `--numprocesses auto` helps mitigate potential thread-blocking issues.
85+
86+
7887
Videos of the test playback will be available in `$PWD/videos/`. To disabled the browser
7988
runtime preview of what is happening while the test runs, pass the `--headed` option to `pytest`. You
8089
can also add the `--slowmo=$MILLI_SECONDS` option to introduce a delay before each
@@ -188,3 +197,17 @@ If your test suit presents a need for a more complex sequence of actions or spec
188197
parsing around the contents present in each page, you can create
189198
your own handler to execute the auxiliary actions while the test is running. Check the
190199
`handlers.py` over some examples of how that's being done.
200+
201+
202+
## Debugging Playwright tests
203+
204+
Playwright supports a debug mode called
205+
[Inspector](https://playwright.dev/python/docs/debug#playwright-inspector) that can be
206+
used to inspect the browser and the page while the test is running. To enabled this
207+
debugging option within the tests execution you can pass the `PWDEBUG=1` variable within
208+
your test execution command.
209+
210+
For example, to run a single test with the debug mode enabled, you can use the following
211+
```bash
212+
PWDEBUG=1 pytest -s test_playwright.py::test_notebook --numprocesses 1
213+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
playwright==1.50.0
2+
pytest==8.0.0
3+
pytest-playwright==0.7.0
4+
pytest-xdist==3.6.1

tests/tests_e2e/playwright/test_playwright.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ def test_login_logout(navigator):
3030
)
3131
@login_parameterized()
3232
def test_navbar_services(navigator, services):
33-
navigator.page.goto(navigator.nebari_url + "hub/home")
33+
home_url = navigator._nebari_url / "hub/home"
34+
navigator.page.goto(home_url.human_repr())
3435
navigator.page.wait_for_load_state("networkidle")
3536
navbar_items = navigator.page.locator("#thenavbar").get_by_role("link")
3637
navbar_items_names = [item.text_content() for item in navbar_items.all()]

0 commit comments

Comments
 (0)