Skip to content

Commit b6f3e99

Browse files
authored
Add UI tests (#17)
1 parent 8589a03 commit b6f3e99

23 files changed

+4806
-2
lines changed

.eslintignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
node_modules
2+
dist
3+
coverage
4+
**/*.d.ts
5+
**/*.js
6+
tests
7+
ui-tests
8+
**/build/

.github/workflows/build.yml

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ jobs:
7575
sudo rm -rf $(which node)
7676
7777
cp ./jupyter_suggestions_core/dist/jupyter_suggestions*.whl .
78+
cp ./jupyter_suggestions_rtc/dist/jupyter_suggestions*.whl .
7879
pip install "jupyterlab>=4.0.0,<5" jupyter_suggestions*.whl
7980
8081
@@ -91,9 +92,84 @@ jobs:
9192
- uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
9293
- uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1
9394

95+
integration-tests:
96+
name: Integration tests
97+
needs: build
98+
runs-on: ubuntu-latest
99+
strategy:
100+
fail-fast: false
101+
matrix:
102+
backend: ['local', 'rtc']
103+
104+
env:
105+
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers
106+
107+
steps:
108+
- name: Checkout
109+
uses: actions/checkout@v4
110+
111+
- name: Install Python
112+
uses: actions/setup-python@v5
113+
with:
114+
python-version: '3.11'
115+
architecture: 'x64'
116+
117+
- uses: actions/download-artifact@v4
118+
with:
119+
name: extension-artifacts
120+
121+
- name: Install base package
122+
run: |
123+
set -eux
124+
cp ./jupyter_suggestions_core/dist/jupyter_suggestions_core*.whl .
125+
pip install "jupyterlab>=4.0.0,<5" jupyter_suggestions_core*.whl
126+
127+
- name: Install rtc package
128+
if: matrix.backend == 'rtc'
129+
run: |
130+
set -eux
131+
cp ./jupyter_suggestions_rtc/dist/jupyter_suggestions_rtc*.whl .
132+
pip install jupyter_suggestions_rtc*.whl
133+
134+
- name: Install dependencies
135+
shell: bash -l {0}
136+
working-directory: ui-tests
137+
env:
138+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
139+
run: jlpm install
140+
141+
- name: Set up browser cache
142+
uses: actions/cache@v4
143+
with:
144+
path: |
145+
${{ github.workspace }}/pw-browsers
146+
key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }}
147+
148+
- name: Install browser
149+
shell: bash -l {0}
150+
run: npx playwright install chromium
151+
working-directory: ui-tests
152+
153+
- name: Execute integration tests
154+
shell: bash -l {0}
155+
working-directory: ui-tests
156+
run: |
157+
npx playwright test
158+
env:
159+
BACKEND: ${{ matrix.backend }}
160+
161+
- name: Upload Playwright Test report
162+
id: upload-galata-artifact
163+
if: always()
164+
uses: actions/upload-artifact@v4
165+
with:
166+
name: jupyter-suggestions-playwright-${{ matrix.backend }}-tests
167+
path: |
168+
ui-tests/test-results
169+
ui-tests/playwright-report
94170
build-lite:
95171
name: Build JupyterLite
96-
needs: build
172+
needs: [test_isolated, integration-tests]
97173
runs-on: ubuntu-latest
98174

99175
steps:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,8 @@ dmypy.json
122122

123123
python/jupyter_suggestions_rtc/jupyter_suggestions_rtc/labextension
124124
python/jupyter_suggestions_rtc/jupyter_suggestions_rtc/_version.py
125+
126+
# Integration tests
127+
**/ui-tests/test-results/
128+
**/ui-tests/playwright-report/
129+
**/*/.jupyter_ystore.db

packages/base/src/suggestionCellMenu/cellToolbarMenu.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,14 @@ function _CellToolbarMenuReact(props: IProps) {
107107

108108
React.useEffect(() => {
109109
const handleClickOutside = (event: MouseEvent) => {
110-
if (menuRef.current && !menuRef.current.contains(event.target as any)) {
110+
const target = event.target as any;
111+
if (!target) {
112+
return;
113+
}
114+
if (currentDiv.current && currentDiv.current.contains(target)) {
115+
return;
116+
}
117+
if (menuRef.current && !menuRef.current.contains(target)) {
111118
setOpen(false);
112119
}
113120
};
@@ -117,6 +124,7 @@ function _CellToolbarMenuReact(props: IProps) {
117124
document.removeEventListener('mousedown', handleClickOutside);
118125
};
119126
}, [currentDiv]);
127+
120128
const cellSuggestions = cell?.model?.id
121129
? (suggestionModel.getCellSuggestions({
122130
cellId: cell.model.id

python/jupyter_suggestions_core/style/base.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,22 @@
1717
color: gray;
1818
font-style: italic;
1919
}
20+
21+
.jp-MenuBar-anonymousIcon span {
22+
width: 24px;
23+
text-align: center;
24+
fill: var(--jp-ui-font-color1);
25+
color: var(--jp-ui-font-color1);
26+
}
27+
28+
.jp-MenuBar-anonymousIcon {
29+
position: absolute;
30+
top: 1px;
31+
left: 8px;
32+
width: 24px;
33+
height: 24px;
34+
display: flex;
35+
align-items: center;
36+
vertical-align: middle;
37+
border-radius: 100%;
38+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Server configuration for integration tests.
2+
3+
!! Never use this configuration in production because it
4+
opens the server to the world and provide access to JupyterLab
5+
JavaScript objects through the global window variable.
6+
"""
7+
import os
8+
from jupyterlab.galata import configure_jupyter_server
9+
10+
backend = os.getenv("BACKEND", None)
11+
configure_jupyter_server(c) # noqa F821
12+
c.LabApp.collaborative = backend == 'rtc' # noqa F821
13+
# Uncomment to set server log level to debug level
14+
# c.ServerApp.log_level = "DEBUG"

ui-tests/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "jupyter-suggestions-ui-tests",
3+
"version": "1.0.0",
4+
"description": "Jupyter suggestions Integration Tests",
5+
"private": true,
6+
"scripts": {
7+
"start": "jupyter lab --config jupyter_server_test_config.py",
8+
"test": "npx playwright test --workers 1",
9+
"test:update": "npx playwright test --update-snapshots",
10+
"test:debug": "PWDEBUG=1 npx playwright test --workers 1"
11+
},
12+
"devDependencies": {
13+
"@jupyterlab/galata": "^5.2.5",
14+
"@playwright/test": "^1.32.0"
15+
}
16+
}

ui-tests/playwright.config.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Configuration for Playwright using default from @jupyterlab/galata
3+
*/
4+
const baseConfig = require('@jupyterlab/galata/lib/playwright-config');
5+
6+
module.exports = {
7+
...baseConfig,
8+
webServer: {
9+
command: 'jlpm start',
10+
url: 'http://localhost:8888/lab',
11+
timeout: 120 * 1000,
12+
reuseExistingServer: false
13+
},
14+
retries: 0,
15+
use: {
16+
...baseConfig.use,
17+
trace: 'off',
18+
video: 'retain-on-failure',
19+
viewport: { width: 1920, height: 1080 },
20+
},
21+
expect: {
22+
toMatchSnapshot: {
23+
maxDiffPixelRatio: 0.002,
24+
},
25+
},
26+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "b9356f9a-ea42-4050-a3a9-5684edd6bf18",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"def foo():\n",
11+
" print(123)\n",
12+
" return 0"
13+
]
14+
},
15+
{
16+
"cell_type": "markdown",
17+
"id": "3ff6c3ec-e344-4262-818a-a1ced33c3cac",
18+
"metadata": {},
19+
"source": [
20+
"## This is a markdown cell"
21+
]
22+
},
23+
{
24+
"cell_type": "raw",
25+
"id": "dadedd23-c079-4149-8db6-c8e305e55462",
26+
"metadata": {},
27+
"source": [
28+
"This a a raw cell"
29+
]
30+
}
31+
],
32+
"metadata": {
33+
"kernelspec": {
34+
"display_name": "Python 3 (ipykernel)",
35+
"language": "python",
36+
"name": "python3"
37+
},
38+
"language_info": {
39+
"codemirror_mode": {
40+
"name": "ipython",
41+
"version": 3
42+
},
43+
"file_extension": ".py",
44+
"mimetype": "text/x-python",
45+
"name": "python",
46+
"nbconvert_exporter": "python",
47+
"pygments_lexer": "ipython3",
48+
"version": "3.11.11"
49+
}
50+
},
51+
"nbformat": 4,
52+
"nbformat_minor": 5
53+
}

ui-tests/tests/ui.spec.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { expect, test, galata } from '@jupyterlab/galata';
2+
import path from 'path';
3+
4+
const BACKEND = process.env.BACKEND ?? 'rtc';
5+
test.use({ autoGoto: false });
6+
7+
test.describe('UI Test', () => {
8+
test.describe('Extension activation test', () => {
9+
test('should emit an activation console message', async ({
10+
page,
11+
request
12+
}) => {
13+
const logs: string[] = [];
14+
15+
page.on('console', message => {
16+
logs.push(message.text());
17+
});
18+
19+
await page.goto();
20+
const expectedExtension = BACKEND === 'rtc' ? 7 : 6;
21+
expect(logs.filter(s => s.includes('@jupyter/suggestions'))).toHaveLength(
22+
expectedExtension
23+
);
24+
await page.getByTitle('Jupyter Suggestions');
25+
expect(await page.screenshot()).toMatchSnapshot({
26+
name: `${BACKEND}-main-page.png`
27+
});
28+
});
29+
});
30+
test.describe('Panel activation test', () => {
31+
test('should add the suggestion panel to the right', async ({
32+
page,
33+
request
34+
}) => {
35+
await page.goto();
36+
37+
await page.sidebar.open('right');
38+
await page.getByTitle('Jupyter Suggestions').click();
39+
await page.getByTitle('All Suggestions');
40+
expect(await page.screenshot()).toMatchSnapshot({
41+
name: `${BACKEND}-side-panel.png`
42+
});
43+
});
44+
});
45+
});
46+
47+
test.describe('Notebook Test', () => {
48+
test.beforeEach(async ({ page, tmpPath }) => {
49+
await page.contents.uploadDirectory(
50+
path.resolve(__dirname, './notebooks'),
51+
tmpPath
52+
);
53+
await page.filebrowser.openDirectory(tmpPath);
54+
});
55+
56+
test('Should add suggestion widget', async ({ page, tmpPath }) => {
57+
await page.goto();
58+
const notebook = 'test.ipynb';
59+
await page.sidebar.open('right');
60+
await page.getByTitle('Jupyter Suggestions').click();
61+
await page.getByTitle('All Suggestions');
62+
63+
await page.notebook.openByPath(`${tmpPath}/${notebook}`);
64+
await page.notebook.activate(notebook);
65+
66+
await page.notebook.selectCells(0);
67+
await page.getByRole('button', { name: 'Suggestion menu' }).click();
68+
await page.getByText('Suggest change').click();
69+
await page.waitForTimeout(500);
70+
await page.notebook.selectCells(1);
71+
await page.getByRole('button', { name: 'Suggestion menu' }).click();
72+
await page.getByText('Suggest delete').click();
73+
await page.waitForTimeout(500);
74+
expect(await page.screenshot()).toMatchSnapshot({
75+
name: `${BACKEND}-add-suggestion.png`
76+
});
77+
await page
78+
.getByLabel('All Suggestions (2)', { exact: true })
79+
.getByText('def foo(): print(123) return')
80+
.fill('def foobar():\n print(123)\n return 123');
81+
await page.waitForTimeout(500);
82+
expect(
83+
await page.getByLabel('All Suggestions (2)', { exact: true }).screenshot()
84+
).toMatchSnapshot({
85+
name: `${BACKEND}-change-cell-content.png`
86+
});
87+
await page.getByLabel('Accept suggestion').nth(2).click();
88+
await page.waitForTimeout(500);
89+
expect(await page.screenshot()).toMatchSnapshot({
90+
name: `${BACKEND}-accept-change-suggestion.png`
91+
});
92+
await page.getByLabel('Accept suggestion').nth(1).click();
93+
await page.waitForTimeout(500);
94+
expect(await page.screenshot()).toMatchSnapshot({
95+
name: `${BACKEND}-accept-delete-suggestion.png`
96+
});
97+
});
98+
});

0 commit comments

Comments
 (0)