Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 18 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,13 @@ jobs:
working-directory: dist

- name: Install Galata
run: yarn install
run: |
yarn install
yarn playwright install chromium
working-directory: ui-tests

- name: Launch JupyterLab
run: yarn run start-jlab:detached
run: yarn run start:detached
working-directory: ui-tests

- name: Wait for JupyterLab
Expand All @@ -137,7 +139,20 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: ui-test-output
path: ui-tests/test-output
path: ui-tests/playwright-report

- name: Update snapshots
if: failure()
run: |
cd ui-tests
yarn run test:update

- name: Upload updated snapshots
if: failure()
uses: actions/upload-artifact@v2
with:
name: ipywidgets-updated-snapshots
path: ui-tests/tests

install:
runs-on: ${{ matrix.os }}-latest
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ pip-log.txt
.coverage
.tox
nosetests.xml
ui-tests/test-output/*
ui-tests/test-results
ui-tests/playwright-report

# Translations
*.mo
Expand Down
4 changes: 2 additions & 2 deletions test-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ dependencies:
- pandas >=1.0.0,<2.0.0
- bqscales >=0.2.2,<0.3
- scipy
- jupyterlab=3.0.11 # to build the lab federated bundle
- jupyter-packaging # to build the wheel
- jupyterlab=3.2
- jupyter-packaging
- pytest
- nbval
- pytest-cov
Expand Down
3 changes: 0 additions & 3 deletions ui-tests/galata-config.json

This file was deleted.

5 changes: 4 additions & 1 deletion ui-tests/jupyter_server_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from tempfile import mkdtemp

c.ServerApp.port = 8888
c.ServerApp.token = ""
c.ServerApp.password = ""
c.ServerApp.disable_check_xsrf = True
c.ServerApp.open_browser = False
c.LabApp.open_browser = False
c.ServerApp.root_dir = mkdtemp(prefix='galata-test-')

c.LabApp.expose_app_in_browser = True
13 changes: 7 additions & 6 deletions ui-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
"description": "bqplot UI Tests",
"private": true,
"scripts": {
"start-jlab": "jupyter lab --config ./jupyter_server_config.py",
"start-jlab:detached": "yarn run start-jlab&",
"clean": "rimraf tests/notebooks/.ipynb_checkpoints && rimraf test-output",
"test": "yarn run clean && galata --image-match-threshold 0.33",
"update-references": "galata --update-references"
"start": "jupyter lab --config ./jupyter_server_config.py",
"start:detached": "yarn run start&",
"test": "playwright test",
"test:debug": "PWDEBUG=1 playwright test",
"test:report": "http-server ./playwright-report -a localhost -o",
"test:update": "playwright test --update-snapshots"
},
"author": "bqplot",
"license": "Apache-2.0",
"dependencies": {
"@jupyterlab/galata": "3.0.11-2",
"@jupyterlab/galata": "~4.0.2",
"klaw-sync": "^6.0.0",
"rimraf": "^3.0.2"
}
Expand Down
7 changes: 7 additions & 0 deletions ui-tests/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const baseConfig = require('@jupyterlab/galata/lib/playwright-config');

module.exports = {
...baseConfig,
timeout: 600000,
retries: 1,
};
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
166 changes: 62 additions & 104 deletions ui-tests/tests/bqplot.test.ts
Original file line number Diff line number Diff line change
@@ -1,171 +1,129 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { galata, describe, test } from '@jupyterlab/galata';
import { IJupyterLabPageFixture, test } from '@jupyterlab/galata';
import { expect } from '@playwright/test';
import * as path from 'path';
const klaw = require('klaw-sync');

jest.setTimeout(600000);

const filterUpdateNotebooks = item => {
const basename = path.basename(item.path);
return basename.includes('_update');
}

const testCellOutputs = async (theme: 'JupyterLab Light' | 'JupyterLab Dark') => {
const paths = klaw('tests/notebooks', {filter: item => !filterUpdateNotebooks(item), nodir: true});
const testCellOutputs = async (page: IJupyterLabPageFixture, tmpPath: string, theme: 'JupyterLab Light' | 'JupyterLab Dark') => {
const paths = klaw(path.resolve(__dirname, './notebooks'), {filter: item => !filterUpdateNotebooks(item), nodir: true});
const notebooks = paths.map(item => path.basename(item.path));

let results = [];

const contextPrefix = theme == 'JupyterLab Light' ? 'light_' : 'dark_';
galata.theme.setTheme(theme);
page.theme.setTheme(theme);

for (const notebook of notebooks) {
galata.context.capturePrefix = contextPrefix + notebook;
let results = [];

await galata.notebook.open(notebook);
expect(await galata.notebook.isOpen(notebook)).toBeTruthy();
await galata.notebook.activate(notebook);
expect(await galata.notebook.isActive(notebook)).toBeTruthy();
await page.notebook.openByPath(`${tmpPath}/${notebook}`);
await page.notebook.activate(notebook);

let numCellImages = 0;

const getCaptureImageName = (id: number): string => {
return `cell-${id}`;
const getCaptureImageName = (contextPrefix: string, notebook: string, id: number): string => {
return `${contextPrefix}-${notebook}-cell-${id}.png`;
};

await galata.notebook.runCellByCell({
await page.notebook.runCellByCell({
onAfterCellRun: async (cellIndex: number) => {
const cell = await galata.notebook.getCellOutput(cellIndex);
const cell = await page.notebook.getCellOutput(cellIndex);
if (cell) {
if (
await galata.capture.screenshot(
getCaptureImageName(numCellImages),
cell
)
) {
numCellImages++;
}
results.push(await cell.screenshot());
numCellImages++;
}
}
});

await page.notebook.save();

for (let c = 0; c < numCellImages; ++c) {
results.push(await galata.capture.compareScreenshot(getCaptureImageName(c)));
expect(results[c]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, c));
}

await galata.notebook.close(true);
}

for (const result of results) {
expect(result).toBe('same');
await page.notebook.close(true);
}
}

const testPlotUpdates = async (theme: 'JupyterLab Light' | 'JupyterLab Dark') => {
const paths = klaw('tests/notebooks', {filter: item => filterUpdateNotebooks(item), nodir: true});
const testPlotUpdates = async (page: IJupyterLabPageFixture, tmpPath: string, theme: 'JupyterLab Light' | 'JupyterLab Dark') => {
const paths = klaw(path.resolve(__dirname, './notebooks'), {filter: item => filterUpdateNotebooks(item), nodir: true});
const notebooks = paths.map(item => path.basename(item.path));

let results = [];

const contextPrefix = theme == 'JupyterLab Light' ? 'light_' : 'dark_';
galata.theme.setTheme(theme);
page.theme.setTheme(theme);

for (const notebook of notebooks) {
galata.context.capturePrefix = contextPrefix + notebook;

await galata.notebook.open(notebook);
expect(await galata.notebook.isOpen(notebook)).toBeTruthy();
await galata.notebook.activate(notebook);
expect(await galata.notebook.isActive(notebook)).toBeTruthy();
let results = [];

let numCellImages = 0;
await page.notebook.openByPath(`${tmpPath}/${notebook}`);
await page.notebook.activate(notebook);

const getCaptureImageName = (id: number): string => {
return `cell-${id}`;
const getCaptureImageName = (contextPrefix: string, notebook: string, id: number): string => {
return `${contextPrefix}-${notebook}-cell-${id}.png`;
};

await galata.notebook.runCellByCell({
let cellCount = 0;
await page.notebook.runCellByCell({
onAfterCellRun: async (cellIndex: number) => {
// Always get first cell output which must contain the plot
const cell = await galata.notebook.getCellOutput(0);
const cell = await page.notebook.getCellOutput(0);
if (cell) {
if (
await galata.capture.screenshot(
getCaptureImageName(numCellImages),
cell
)
) {
numCellImages++;
}
results.push(await cell.screenshot());
cellCount++;
}
}
});

for (let c = 0; c < numCellImages; ++c) {
results.push(await galata.capture.compareScreenshot(getCaptureImageName(c)));
}
await page.notebook.save();

await galata.notebook.close(true);
}
for (let i = 0; i < cellCount; i++) {
expect(results[i]).toMatchSnapshot(getCaptureImageName(contextPrefix, notebook, i));
}

for (const result of results) {
expect(result).toBe('same');
await page.notebook.close(true);
}
};

describe('bqplot Visual Regression', () => {
beforeAll(async () => {
await galata.resetUI();
});

afterAll(async () => {
galata.context.capturePrefix = '';
});

test('Upload files to JupyterLab', async () => {
await galata.contents.moveDirectoryToServer(
path.resolve(__dirname, `./notebooks`),
'uploaded'
test.describe('bqplot Visual Regression', () => {
test.beforeEach(async ({ page, tmpPath }) => {
await page.contents.uploadDirectory(
path.resolve(__dirname, './notebooks'),
tmpPath
);
expect(
await galata.contents.fileExists('uploaded/scatter.ipynb')
).toBeTruthy();
});

test('Refresh File Browser', async () => {
await galata.filebrowser.refresh();
});

test('Open directory uploaded', async () => {
await galata.filebrowser.openDirectory('uploaded');
expect(
await galata.filebrowser.isFileListedInBrowser('scatter.ipynb')
).toBeTruthy();
});

test('Light theme: Check bqplot first renders', async () => {
await testCellOutputs('JupyterLab Light');
});

test('Dark theme: Check bqplot first renders', async () => {
await testCellOutputs('JupyterLab Dark');
await page.filebrowser.openDirectory(tmpPath);
});

test('Light theme: Check bqplot update plot properties', async () => {
await testPlotUpdates('JupyterLab Light');
test('Light theme: Check bqplot first renders', async ({
page,
tmpPath,
}) => {
await testCellOutputs(page, tmpPath, 'JupyterLab Light');
});

test('Dark theme: Check bqplot update plot properties', async () => {
await testPlotUpdates('JupyterLab Dark');
test('Dark theme: Check bqplot first renders', async ({
page,
tmpPath,
}) => {
await testCellOutputs(page, tmpPath, 'JupyterLab Dark');
});

test('Open home directory', async () => {
await galata.filebrowser.openHomeDirectory();
test('Light theme: Check bqplot update plot properties', async ({
page,
tmpPath,
}) => {
await testPlotUpdates(page, tmpPath, 'JupyterLab Light');
});

test('Delete uploaded directory', async () => {
await galata.contents.deleteDirectory('uploaded');
test('Dark theme: Check bqplot update plot properties', async ({
page,
tmpPath,
}) => {
await testPlotUpdates(page, tmpPath, 'JupyterLab Dark');
});
});
Loading