Skip to content

Commit

Permalink
UI visual regression testing to cover UI widgets visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
Ygnas committed Sep 18, 2024
1 parent 9faa4dc commit 942fb9f
Show file tree
Hide file tree
Showing 22 changed files with 3,140 additions and 0 deletions.
110 changes: 110 additions & 0 deletions .github/workflows/ui_notebooks_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: UI notebooks tests

on: [pull_request]

concurrency:
group: ${{ github.head_ref }}-${{ github.workflow }}
cancel-in-progress: true

env:
CODEFLARE_OPERATOR_IMG: "quay.io/project-codeflare/codeflare-operator:dev"

jobs:
verify-0_basic_ray:
runs-on: ubuntu-20.04-4core

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive

- name: Checkout common repo code
uses: actions/checkout@v4
with:
repository: "project-codeflare/codeflare-common"
ref: "main"
path: "common"

- name: Checkout CodeFlare operator repository
uses: actions/checkout@v4
with:
repository: project-codeflare/codeflare-operator
path: codeflare-operator

- name: Set Go
uses: actions/setup-go@v5
with:
go-version-file: "./codeflare-operator/go.mod"
cache-dependency-path: "./codeflare-operator/go.sum"

- name: Set up gotestfmt
uses: gotesttools/gotestfmt-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up specific Python version
uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: "pip" # caching pip dependencies

- name: Setup and start KinD cluster
uses: ./common/github-actions/kind

- name: Deploy CodeFlare stack
id: deploy
run: |
cd codeflare-operator
echo Setting up CodeFlare stack
make setup-e2e
echo Deploying CodeFlare operator
make deploy -e IMG="${CODEFLARE_OPERATOR_IMG}" -e ENV="e2e"
kubectl wait --timeout=120s --for=condition=Available=true deployment -n openshift-operators codeflare-operator-manager
cd ..
- name: Setup Guided notebooks execution
run: |
echo "Installing papermill and dependencies..."
pip install poetry ipython ipykernel jupyterlab
poetry config virtualenvs.create false
echo "Installing SDK..."
poetry install --with test,docs
- name: Install Yarn dependencies
run: |
poetry run yarn install
poetry run yarn playwright install chromium
working-directory: ui-tests

- name: Fix 0_basic_ray.ipynb notebook for test
run: |
# Remove login/logout cells, as KinD doesn't support authentication using token
jq -r 'del(.cells[] | select(.source[] | contains("Create authentication object for user permissions")))' 0_basic_ray.ipynb > 0_basic_ray.ipynb.tmp && mv 0_basic_ray.ipynb.tmp 0_basic_ray.ipynb
jq -r 'del(.cells[] | select(.source[] | contains("auth.logout()")))' 0_basic_ray.ipynb > 0_basic_ray.ipynb.tmp && mv 0_basic_ray.ipynb.tmp 0_basic_ray.ipynb
# Set explicit namespace as SDK need it (currently) to resolve local queues
sed -i "s/head_memory=2,/head_memory=2, namespace='default',/" 0_basic_ray.ipynb
working-directory: demo-notebooks/guided-demos

- name: Run UI notebook tests
run: |
set -euo pipefail
poetry run yarn test
working-directory: ui-tests

- name: Upload Playwright Test assets
if: always()
uses: actions/upload-artifact@v4
with:
name: ipywidgets-test-assets
path: |
ui-tests/test-results
- name: Upload Playwright Test report
if: always()
uses: actions/upload-artifact@v4
with:
name: ipywidgets-test-report
path: |
ui-tests/playwright-report
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ Pipfile.lock
build/
tls-cluster-namespace
quicktest.yaml
node_modules
.DS_Store
ui-tests/playwright-report
ui-tests/test-results
4 changes: 4 additions & 0 deletions ui-tests/.yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
disable-self-update-check true
ignore-optional true
network-timeout "300000"
registry "https://registry.npmjs.org/"
6 changes: 6 additions & 0 deletions ui-tests/jupyter_server_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from jupyterlab.galata import configure_jupyter_server

configure_jupyter_server(c)

# Uncomment to set server log level to debug level
# c.ServerApp.log_level = "DEBUG"
22 changes: 22 additions & 0 deletions ui-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@jupyter-widgets/ui-tests",
"private": true,
"version": "0.1.0",
"description": "ipywidgets UI Tests",
"scripts": {
"start": "jupyter lab --config ./jupyter_server_config.py",
"start:detached": "jlpm start&",
"test": "npx playwright test",
"test:debug": "PWDEBUG=1 npx playwright test",
"test:report": "http-server ./playwright-report -a localhost -o",
"test:update": "npx playwright test --update-snapshots",
"deduplicate": "jlpm && yarn-deduplicate -s fewer --fail"
},
"author": "Project Jupyter",
"license": "BSD-3-Clause",
"devDependencies": {
"@jupyterlab/galata": "^5.0.1",
"@playwright/test": "^1.32.0",
"yarn-deduplicate": "^6.0.1"
}
}
13 changes: 13 additions & 0 deletions ui-tests/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const baseConfig = require('@jupyterlab/galata/lib/playwright-config');

module.exports = {
...baseConfig,
timeout: 240000,
webServer: {
command: 'yarn start',
url: 'http://localhost:8888/lab',
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
retries: 0,
};
52 changes: 52 additions & 0 deletions ui-tests/tests/0_basic_ray.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { test } from "@jupyterlab/galata";
import { expect } from "@playwright/test";
import * as path from "path";

test.setTimeout(460000);

test.describe("Visual Regression", () => {
test.beforeEach(async ({ page, tmpPath }) => {
await page.contents.uploadDirectory(
path.resolve(__dirname, "../../demo-notebooks/guided-demos"),
tmpPath
);
await page.filebrowser.openDirectory(tmpPath);
});

test("Run notebook 0_basic_ray.ipynb and capture cell outputs", async ({
page,
tmpPath,
}) => {
const notebook = "0_basic_ray.ipynb";
await page.notebook.openByPath(`${tmpPath}/${notebook}`);
await page.notebook.activate(notebook);

const captures: (Buffer | null)[] = []; // Array to store cell screenshots
const cellCount = await page.notebook.getCellCount();

// Run all cells and capture their screenshots
await page.notebook.runCellByCell({
onAfterCellRun: async (cellIndex: number) => {
const cell = await page.notebook.getCellOutput(cellIndex);
if (cell && (await cell.isVisible())) {
captures[cellIndex] = await cell.screenshot(); // Save the screenshot by cell index
}
},
});

await page.notebook.save();

// Ensure that each cell's screenshot is captured
for (let i = 0; i < cellCount; i++) {
const image = `widgets-cell-${i}.png`;

if (captures[i]) {
expect.soft(captures[i]).toMatchSnapshot(image); // Compare pre-existing capture
continue;
}
}
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions ui-tests/tests/notebooks/0_basic_ray.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b55bc3ea-4ce3-49bf-bb1f-e209de8ca47a",
"metadata": {},
"outputs": [],
"source": [
"# Import pieces from codeflare-sdk\n",
"from codeflare_sdk import Cluster, ClusterConfiguration, TokenAuthentication"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0f4bc870-091f-4e11-9642-cba145710159",
"metadata": {},
"outputs": [],
"source": [
"# Create and configure our cluster object\n",
"# 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",
"cluster = Cluster(ClusterConfiguration(\n",
" name='raytest',\n",
" namespace='default',\n",
" head_cpus='500m',\n",
" head_memory=2,\n",
" head_gpus=0, # For GPU enabled workloads set the head_gpus and num_gpus\n",
" num_gpus=0,\n",
" num_workers=2,\n",
" min_cpus='250m',\n",
" max_cpus=1,\n",
" min_memory=1,\n",
" max_memory=2,\n",
" # image=\"\", # Optional Field \n",
" write_to_file=False, # When enabled Ray Cluster yaml files are written to /HOME/.codeflare/resources \n",
" # local_queue=\"local-queue-name\" # Specify the local queue manually\n",
"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f0884bbc-c224-4ca0-98a0-02dfa09c2200",
"metadata": {},
"outputs": [],
"source": [
"# Bring up the cluster\n",
"cluster.up()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d513912",
"metadata": {},
"outputs": [],
"source": [
"cluster.wait_ready()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ce99d84",
"metadata": {},
"outputs": [],
"source": [
"cluster.status()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1767a342",
"metadata": {},
"outputs": [],
"source": [
"cluster.down()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7e9152ce",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.19"
},
"vscode": {
"interpreter": {
"hash": "f9f85f796d01129d0dd105a088854619f454435301f6ffec2fea96ecbd9be4ac"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading

0 comments on commit 942fb9f

Please sign in to comment.