Skip to content

Commit d4ce271

Browse files
Add UI table to regression and functionality tests
1 parent a8ba6f7 commit d4ce271

File tree

5 files changed

+88
-48
lines changed

5 files changed

+88
-48
lines changed

.github/workflows/ui_notebooks_test.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ jobs:
8686
jq -r 'del(.cells[] | select(.source[] | contains("Create authentication object for user permissions")))' 3_widget_example.ipynb > 3_widget_example.ipynb.tmp && mv 3_widget_example.ipynb.tmp 3_widget_example.ipynb
8787
jq -r 'del(.cells[] | select(.source[] | contains("auth.logout()")))' 3_widget_example.ipynb > 3_widget_example.ipynb.tmp && mv 3_widget_example.ipynb.tmp 3_widget_example.ipynb
8888
# Set explicit namespace as SDK need it (currently) to resolve local queues
89-
sed -i "s/head_memory_limits=2,/head_memory_limits=2, namespace='default',/" 3_widget_example.ipynb
89+
sed -i "s/head_memory_limits=2,/head_memory_limits=2, namespace='default', image='quay.io/modh/ray:2.35.0-py39-cu121',/" 3_widget_example.ipynb
90+
sed -i "s/view_clusters()/view_clusters('default')/" 3_widget_example.ipynb
9091
working-directory: demo-notebooks/guided-demos
9192

9293
- name: Run UI notebook tests

demo-notebooks/guided-demos/3_widget_example.ipynb

+14-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"outputs": [],
2020
"source": [
2121
"# Import pieces from codeflare-sdk\n",
22-
"from codeflare_sdk import Cluster, ClusterConfiguration, TokenAuthentication"
22+
"from codeflare_sdk import Cluster, ClusterConfiguration, TokenAuthentication, view_clusters"
2323
]
2424
},
2525
{
@@ -61,7 +61,7 @@
6161
"# Create and configure our cluster object\n",
6262
"# The SDK will try to find the name of your default local queue based on the annotation \"kueue.x-k8s.io/default-queue\": \"true\" unless you specify the local queue manually below\n",
6363
"cluster = Cluster(ClusterConfiguration(\n",
64-
" name='raytest', \n",
64+
" name='raytest',\n",
6565
" head_cpu_requests='500m',\n",
6666
" head_cpu_limits='500m',\n",
6767
" head_memory_requests=2,\n",
@@ -73,12 +73,22 @@
7373
" worker_cpu_limits=1,\n",
7474
" worker_memory_requests=2,\n",
7575
" worker_memory_limits=2,\n",
76-
" # image=\"\", # Optional Field \n",
77-
" write_to_file=False, # When enabled Ray Cluster yaml files are written to /HOME/.codeflare/resources \n",
76+
" # image=\"\", # Optional Field\n",
77+
" write_to_file=False, # When enabled Ray Cluster yaml files are written to /HOME/.codeflare/resources\n",
7878
" # local_queue=\"local-queue-name\" # Specify the local queue manually\n",
7979
"))"
8080
]
8181
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": null,
85+
"id": "3de6403c",
86+
"metadata": {},
87+
"outputs": [],
88+
"source": [
89+
"view_clusters()"
90+
]
91+
},
8292
{
8393
"cell_type": "code",
8494
"execution_count": null,

src/codeflare_sdk/cluster/widgets.py

+10-14
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def _delete_cluster(
253253
plural = "rayclusters"
254254

255255
# Wait for the resource to be deleted
256-
while True:
256+
while timeout > 0:
257257
try:
258258
api_instance.get_namespaced_custom_object(
259259
group=group,
@@ -321,17 +321,13 @@ def _fetch_cluster_data(namespace):
321321
}
322322
return pd.DataFrame(data)
323323

324-
# format_status takes a RayCluster status and applies colors and icons based on the status.
324+
325325
def _format_status(status):
326-
if status == RayClusterStatus.READY:
327-
return '<span style="color: green;">Ready ✓</span>'
328-
elif status == RayClusterStatus.SUSPENDED:
329-
return '<span style="color: #007BFF;">Suspended ❄️</span>'
330-
elif status == RayClusterStatus.FAILED:
331-
return '<span style="color: red;">Failed ✗</span>'
332-
elif status == RayClusterStatus.UNHEALTHY:
333-
return '<span style="color: purple;">Unhealthy</span>'
334-
elif status == RayClusterStatus.UNKNOWN:
335-
return '<span style="color: purple;">Unknown</span>'
336-
else:
337-
return status
326+
status_map = {
327+
RayClusterStatus.READY: '<span style="color: green;">Ready ✓</span>',
328+
RayClusterStatus.SUSPENDED: '<span style="color: #007BFF;">Suspended ❄️</span>',
329+
RayClusterStatus.FAILED: '<span style="color: red;">Failed ✗</span>',
330+
RayClusterStatus.UNHEALTHY: '<span style="color: purple;">Unhealthy</span>',
331+
RayClusterStatus.UNKNOWN: '<span style="color: purple;">Unknown</span>'
332+
}
333+
return status_map.get(status, status)

tests/unit_test.py

+12-21
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,7 @@
7777
is_openshift_cluster,
7878
)
7979

80-
from codeflare_sdk.cluster.widgets import (
81-
cluster_up_down_buttons,
82-
view_clusters,
83-
_on_cluster_click,
84-
_on_delete_button_click,
85-
_on_list_jobs_button_click,
86-
_on_ray_dashboard_button_click,
87-
_format_status,
88-
_fetch_cluster_data,
89-
)
80+
import codeflare_sdk.cluster.widgets as cf_widgets
9081
import pandas as pd
9182

9283
import openshift
@@ -2933,7 +2924,7 @@ def test_cluster_up_down_buttons(mocker):
29332924
MockButton.side_effect = [mock_up_button, mock_down_button]
29342925

29352926
# Call the method under test
2936-
cluster_up_down_buttons(cluster)
2927+
cf_widgets.cluster_up_down_buttons(cluster)
29372928

29382929
# Simulate checkbox being checked or unchecked
29392930
mock_wait_ready_check_box.value = True # Simulate checkbox being checked
@@ -2975,7 +2966,7 @@ def test_view_clusters(mocker):
29752966

29762967
# Return empty dataframe when no clusters are found
29772968
mocker.patch("codeflare_sdk.cluster.cluster.list_all_clusters", return_value=[])
2978-
df = _fetch_cluster_data(namespace="default")
2969+
df = cf_widgets._fetch_cluster_data(namespace="default")
29792970
assert df.empty
29802971

29812972
test_df=pd.DataFrame({
@@ -3022,33 +3013,33 @@ def test_view_clusters(mocker):
30223013
MockOutput.return_value = mock_output
30233014

30243015
# Call the function under test
3025-
view_clusters(namespace="default")
3016+
cf_widgets.view_clusters(namespace="default")
30263017

30273018
# Simulate selecting a cluster
30283019
mock_toggle.value = "test-cluster"
30293020
selection_change = {"new": "test-cluster"}
3030-
_on_cluster_click(selection_change, mock_output, "default", mock_toggle)
3021+
cf_widgets._on_cluster_click(selection_change, mock_output, "default", mock_toggle)
30313022

30323023
# Assert that the toggle options are set correctly
30333024
mock_toggle.observe.assert_called()
30343025

30353026
# Simulate clicking the list jobs button
3036-
_on_list_jobs_button_click(None, mock_toggle, test_df, mock_output, mock_output)
3027+
cf_widgets._on_list_jobs_button_click(None, mock_toggle, test_df, mock_output, mock_output)
30373028
mock_javascript.assert_called()
30383029

30393030
# Simulate clicking the Ray dashboard button
3040-
_on_ray_dashboard_button_click(None, mock_toggle, test_df, mock_output, mock_output)
3031+
cf_widgets._on_ray_dashboard_button_click(None, mock_toggle, test_df, mock_output, mock_output)
30413032
mock_javascript.assert_called()
30423033

30433034
# Simulate clicking the delete button
3044-
_on_delete_button_click(None, mock_toggle, test_df, mock_output, mock_output,
3035+
cf_widgets._on_delete_button_click(None, mock_toggle, test_df, mock_output, mock_output,
30453036
mock_delete_button, mock_list_jobs_button, mock_ray_dashboard_button)
30463037

30473038

30483039
def test_fetch_cluster_data(mocker):
30493040
# Return empty dataframe when no clusters are found
30503041
mocker.patch("codeflare_sdk.cluster.cluster.list_all_clusters", return_value=[])
3051-
df = _fetch_cluster_data(namespace="default")
3042+
df = cf_widgets._fetch_cluster_data(namespace="default")
30523043
assert df.empty
30533044

30543045
# Create mock RayCluster objects
@@ -3088,7 +3079,7 @@ def test_fetch_cluster_data(mocker):
30883079

30893080
with patch('codeflare_sdk.cluster.cluster.list_all_clusters', return_value=[mock_raycluster1, mock_raycluster2]):
30903081
# Call the function under test
3091-
df = _fetch_cluster_data(namespace='default')
3082+
df = cf_widgets._fetch_cluster_data(namespace='default')
30923083

30933084
# Expected DataFrame
30943085
expected_data = {
@@ -3123,11 +3114,11 @@ def test_format_status():
31233114
]
31243115

31253116
for status, expected_output in test_cases:
3126-
assert _format_status(status) == expected_output, f"Failed for status: {status}"
3117+
assert cf_widgets._format_status(status) == expected_output, f"Failed for status: {status}"
31273118

31283119
# Test an unrecognized status
31293120
unrecognized_status = 'NotAStatus'
3130-
assert _format_status(unrecognized_status) == 'NotAStatus', "Failed for unrecognized status"
3121+
assert cf_widgets._format_status(unrecognized_status) == 'NotAStatus', "Failed for unrecognized status"
31313122

31323123

31333124
# Make sure to always keep this function last

ui-tests/tests/widget_notebook_example.test.ts

+50-8
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ test.describe("Visual Regression", () => {
3030
tmpPath,
3131
}) => {
3232
const notebook = "3_widget_example.ipynb";
33+
const namespace = 'default';
3334
await page.notebook.openByPath(`${tmpPath}/${notebook}`);
3435
await page.notebook.activate(notebook);
3536

3637
const captures: (Buffer | null)[] = []; // Array to store cell screenshots
3738
const cellCount = await page.notebook.getCellCount();
39+
console.log(`Cell count: ${cellCount}`);
3840

3941
// Run all cells and capture their screenshots
4042
await page.notebook.runCellByCell({
@@ -59,25 +61,27 @@ test.describe("Visual Regression", () => {
5961
}
6062
}
6163

62-
const widgetCellIndex = 3;
64+
// At this point, all cells have been ran, and their screenshots have been captured.
65+
// We now interact with the widgets in the notebook.
66+
const upDownWidgetCellIndex = 3; // 4 on OpenShift
6367

64-
await waitForWidget(page, widgetCellIndex, 'input[type="checkbox"]');
65-
await waitForWidget(page, widgetCellIndex, 'button:has-text("Cluster Down")');
66-
await waitForWidget(page, widgetCellIndex, 'button:has-text("Cluster Up")');
68+
await waitForWidget(page, upDownWidgetCellIndex, 'input[type="checkbox"]');
69+
await waitForWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Down")');
70+
await waitForWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Up")');
6771

68-
await interactWithWidget(page, widgetCellIndex, 'input[type="checkbox"]', async (checkbox) => {
72+
await interactWithWidget(page, upDownWidgetCellIndex, 'input[type="checkbox"]', async (checkbox) => {
6973
await checkbox.click();
7074
const isChecked = await checkbox.isChecked();
7175
expect(isChecked).toBe(true);
7276
});
7377

74-
await interactWithWidget(page, widgetCellIndex, 'button:has-text("Cluster Down")', async (button) => {
78+
await interactWithWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Down")', async (button) => {
7579
await button.click();
7680
const clusterDownMessage = await page.waitForSelector('text=No instances found, nothing to be done.', { timeout: 5000 });
7781
expect(clusterDownMessage).not.toBeNull();
7882
});
7983

80-
await interactWithWidget(page, widgetCellIndex, 'button:has-text("Cluster Up")', async (button) => {
84+
await interactWithWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Up")', async (button) => {
8185
await button.click();
8286

8387
const successMessage = await page.waitForSelector('text=Ray Cluster: \'raytest\' has successfully been created', { timeout: 10000 });
@@ -95,13 +99,51 @@ test.describe("Visual Regression", () => {
9599

96100
await runPreviousCell(page, cellCount, '(<CodeFlareClusterStatus.READY: 1>, True)');
97101

98-
await interactWithWidget(page, widgetCellIndex, 'button:has-text("Cluster Down")', async (button) => {
102+
await interactWithWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Down")', async (button) => {
99103
await button.click();
100104
const clusterDownMessage = await page.waitForSelector('text=Ray Cluster: \'raytest\' has successfully been deleted', { timeout: 5000 });
101105
expect(clusterDownMessage).not.toBeNull();
102106
});
103107

104108
await runPreviousCell(page, cellCount, '(<CodeFlareClusterStatus.UNKNOWN: 6>, False)');
109+
110+
// view_clusters table with buttons
111+
await interactWithWidget(page, upDownWidgetCellIndex, 'input[type="checkbox"]', async (checkbox) => {
112+
await checkbox.click();
113+
const isChecked = await checkbox.isChecked();
114+
expect(isChecked).toBe(false);
115+
});
116+
117+
await interactWithWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Up")', async (button) => {
118+
await button.click();
119+
const successMessage = await page.waitForSelector('text=Ray Cluster: \'raytest\' has successfully been created', { timeout: 10000 });
120+
expect(successMessage).not.toBeNull();
121+
});
122+
123+
const viewClustersCellIndex = 4; // 5 on OpenShift
124+
await page.notebook.runCell(cellCount - 2, true);
125+
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("Open Ray Dashboard")', async (button) => {
126+
await button.click();
127+
const successMessage = await page.waitForSelector('text=Opening Ray Dashboard for raytest cluster', { timeout: 5000 });
128+
expect(successMessage).not.toBeNull();
129+
});
130+
131+
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("View Jobs")', async (button) => {
132+
await button.click();
133+
const successMessage = await page.waitForSelector('text=Opening Ray Jobs Dashboard for raytest cluster', { timeout: 5000 });
134+
expect(successMessage).not.toBeNull();
135+
});
136+
137+
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("Delete Cluster")', async (button) => {
138+
await button.click();
139+
140+
const noClustersMessage = await page.waitForSelector(`text=No clusters found in the ${namespace} namespace.`, { timeout: 5000 });
141+
expect(noClustersMessage).not.toBeNull();
142+
const successMessage = await page.waitForSelector(`text=Cluster raytest in the ${namespace} namespace was deleted successfully.`, { timeout: 5000 });
143+
expect(successMessage).not.toBeNull();
144+
});
145+
146+
await runPreviousCell(page, cellCount, '(<CodeFlareClusterStatus.UNKNOWN: 6>, False)');
105147
});
106148
});
107149

0 commit comments

Comments
 (0)