diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5cae2e..5fd1f2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,10 @@ on: pull_request: branches: '*' +defaults: + run: + shell: bash -l {0} + jobs: build: runs-on: ubuntu-latest @@ -103,17 +107,26 @@ jobs: with: name: extension-artifacts + - name: Install Conda environment with Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-name: test-env + create-args: >- + pip + - name: Install the extension run: | set -eux - python -m pip install "jupyterlab>=4.0.0,<5" jupyterlite_xeus*.whl + python -m pip install "jupyterlab>=4.0.0,<5" jupyterlite jupyterlite_xeus*.whl - name: Install dependencies working-directory: ui-tests env: YARN_ENABLE_IMMUTABLE_INSTALLS: 0 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: jlpm install + run: | + jlpm install + jlpm run build - name: Set up browser cache uses: actions/cache@v3 diff --git a/.github/workflows/update-integration-tests.yml b/.github/workflows/update-integration-tests.yml index 7ea5a66..c0508e7 100644 --- a/.github/workflows/update-integration-tests.yml +++ b/.github/workflows/update-integration-tests.yml @@ -42,6 +42,12 @@ jobs: jlpm python -m pip install . + - name: Install ui-tests + run: | + jlpm + jlpm run build + working-directory: ui-tests + - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index df4d4f4..c080c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -124,8 +124,9 @@ dmypy.json # Yarn cache .yarn/ - - # experiments experiment.sh -env.yml \ No newline at end of file +env.yml + +# Jupyterlite cache +.jupyterlite.doit.db diff --git a/environment-dev.yaml b/environment-dev.yaml index 5c819f5..da603fe 100644 --- a/environment-dev.yaml +++ b/environment-dev.yaml @@ -4,7 +4,7 @@ channels: dependencies: - black - build - - empack >=3.3 + - empack >=3.3 - hatch-jupyter-builder - hatch-nodejs-version - jupyterlab >=4.0 @@ -18,4 +18,4 @@ dependencies: - ruff - traitlets - typer - - yarn \ No newline at end of file + - yarn diff --git a/ui-tests/build.py b/ui-tests/build.py new file mode 100644 index 0000000..780e0aa --- /dev/null +++ b/ui-tests/build.py @@ -0,0 +1,13 @@ +from pathlib import Path +from subprocess import run + +import jupyterlab + +extra_labextensions_path = str(Path(jupyterlab.__file__).parent / "galata") +cmd = f"jupyter lite build --FederatedExtensionAddon.extra_labextensions_path={extra_labextensions_path}" + +run( + cmd, + check=True, + shell=True, +) diff --git a/ui-tests/environment.yml b/ui-tests/environment.yml new file mode 100644 index 0000000..dd8cf5d --- /dev/null +++ b/ui-tests/environment.yml @@ -0,0 +1,9 @@ +name: jupyterlite-xeus-tests +channels: + - https://repo.mamba.pm/emscripten-forge + - https://repo.mamba.pm/conda-forge +dependencies: + - xeus-python + - xeus-lua + - pandas + - bqplot diff --git a/ui-tests/jupyter-lite.json b/ui-tests/jupyter-lite.json new file mode 100644 index 0000000..6d73c21 --- /dev/null +++ b/ui-tests/jupyter-lite.json @@ -0,0 +1,7 @@ +{ + "jupyter-lite-schema-version": 0, + "jupyter-config-data": { + "appName": "JupyterLite UI Tests", + "exposeAppInBrowser": true + } +} diff --git a/ui-tests/jupyter_lite_config.json b/ui-tests/jupyter_lite_config.json new file mode 100644 index 0000000..11bf946 --- /dev/null +++ b/ui-tests/jupyter_lite_config.json @@ -0,0 +1,5 @@ +{ + "LiteBuildConfig": { + "output_dir": "dist" + } +} diff --git a/ui-tests/jupyter_server_test_config.py b/ui-tests/jupyter_server_test_config.py deleted file mode 100644 index f2a9478..0000000 --- a/ui-tests/jupyter_server_test_config.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Server configuration for integration tests. - -!! Never use this configuration in production because it -opens the server to the world and provide access to JupyterLab -JavaScript objects through the global window variable. -""" -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" diff --git a/ui-tests/package.json b/ui-tests/package.json index 0b09167..dc00b9a 100644 --- a/ui-tests/package.json +++ b/ui-tests/package.json @@ -4,12 +4,20 @@ "description": "JupyterLab @jupyterlite/xeus Integration Tests", "private": true, "scripts": { - "start": "jupyter lab --config jupyter_server_test_config.py", - "test": "jlpm playwright test", - "test:update": "jlpm playwright test --update-snapshots" + "build": "yarn run clean && python build.py", + "clean": "rimraf dist", + "start": "cd dist && python -m http.server -b 127.0.0.1 8000", + "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" }, "devDependencies": { - "@jupyterlab/galata": "^5.0.5", - "@playwright/test": "^1.37.0" + "@playwright/test": "^1.37.0", + "rimraf": "^5.0.5" + }, + "dependencies": { + "@jupyterlab/galata": "~5.0.5" } } diff --git a/ui-tests/playwright.config.js b/ui-tests/playwright.config.js index 9ece6fa..e6f28f9 100644 --- a/ui-tests/playwright.config.js +++ b/ui-tests/playwright.config.js @@ -1,14 +1,28 @@ -/** - * Configuration for Playwright using default from @jupyterlab/galata - */ const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); module.exports = { ...baseConfig, - webServer: { - command: 'jlpm start', - url: 'http://localhost:8888/lab', - timeout: 120 * 1000, - reuseExistingServer: !process.env.CI - } + reporter: [[process.env.CI ? 'dot' : 'list'], ['html']], + use: { + acceptDownloads: true, + appPath: '', + autoGoto: false, + baseURL: 'http://localhost:8000', + trace: 'on-first-retry', + video: 'retain-on-failure' + }, + retries: 1, + expect: { + toMatchSnapshot: { + maxDiffPixelRatio: 0.05 + } + }, + webServer: [ + { + command: 'yarn start', + port: 8000, + timeout: 120 * 1000, + reuseExistingServer: true + } + ] }; diff --git a/ui-tests/tests/jupyterlite_xeus.spec.ts b/ui-tests/tests/jupyterlite_xeus.spec.ts index bef0b61..cc58405 100644 --- a/ui-tests/tests/jupyterlite_xeus.spec.ts +++ b/ui-tests/tests/jupyterlite_xeus.spec.ts @@ -1,19 +1,46 @@ -import { expect, test } from '@jupyterlab/galata'; +import { test } from '@jupyterlab/galata'; -/** - * Don't load JupyterLab webpage before running the tests. - * This is required to ensure we capture all log messages. - */ -test.use({ autoGoto: false }); +import { expect } from '@playwright/test'; -test('should emit an activation console message', async ({ page }) => { - const logs: string[] = []; +test.describe('General Tests', () => { + test.beforeEach(({ page }) => { + page.setDefaultTimeout(600000); - page.on('console', message => { - logs.push(message.text()); + page.on('console', message => { + console.log('CONSOLE MSG ---', message.text()); + }); }); - await page.goto(); + test('Launcher should contain xeus-python and xeus-lua kernels', async ({ + page + }) => { + await page.goto('lab/index.html'); - expect(1).toEqual(1); + expect(await page.screenshot()).toMatchSnapshot( + 'jupyter-xeus-launcher.png' + ); + }); + + test('xeus-python should execute some code', async ({ page }) => { + await page.goto('lab/index.html'); + + // Launch a Python notebook + const xpython = page.locator('[title="Python 3.12 (XPython)"]').first(); + await xpython.click(); + + // Wait for kernel to be idle + await page.locator('#jp-main-statusbar').getByText('Idle').waitFor(); + + await page.notebook.addCell('code', 'import bqplot; print("ok")'); + await page.notebook.runCell(1); + + // Wait for kernel to be idle + await page.locator('#jp-main-statusbar').getByText('Idle').waitFor(); + + const cell = await page.notebook.getCellOutput(1); + + expect(await cell?.screenshot()).toMatchSnapshot( + 'jupyter-xeus-execute.png' + ); + }); }); diff --git a/ui-tests/tests/jupyterlite_xeus.spec.ts-snapshots/jupyter-xeus-launcher-linux.png b/ui-tests/tests/jupyterlite_xeus.spec.ts-snapshots/jupyter-xeus-launcher-linux.png new file mode 100644 index 0000000..e18b914 Binary files /dev/null and b/ui-tests/tests/jupyterlite_xeus.spec.ts-snapshots/jupyter-xeus-launcher-linux.png differ diff --git a/ui-tests/yarn.lock b/ui-tests/yarn.lock index 2ecba77..ea63c1c 100644 --- a/ui-tests/yarn.lock +++ b/ui-tests/yarn.lock @@ -665,7 +665,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/galata@npm:^5.0.5": +"@jupyterlab/galata@npm:~5.0.5": version: 5.0.10 resolution: "@jupyterlab/galata@npm:5.0.10" dependencies: @@ -957,8 +957,9 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlite/xeus-ui-tests@workspace:." dependencies: - "@jupyterlab/galata": ^5.0.5 + "@jupyterlab/galata": ~5.0.5 "@playwright/test": ^1.37.0 + rimraf: ^5.0.5 languageName: unknown linkType: soft @@ -2425,7 +2426,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": version: 10.3.10 resolution: "glob@npm:10.3.10" dependencies: @@ -3199,6 +3200,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^5.0.5": + version: 5.0.5 + resolution: "rimraf@npm:5.0.5" + dependencies: + glob: ^10.3.7 + bin: + rimraf: dist/esm/bin.mjs + checksum: d66eef829b2e23b16445f34e73d75c7b7cf4cbc8834b04720def1c8f298eb0753c3d76df77325fad79d0a2c60470525d95f89c2475283ad985fd7441c32732d1 + languageName: node + linkType: hard + "robust-predicates@npm:^3.0.0": version: 3.0.2 resolution: "robust-predicates@npm:3.0.2"