diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a80a9a1e..1a3ccb62 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,8 @@ jobs: diff ../hello-world/.eslintignore .eslintignore diff ../hello-world/.eslintrc.js .eslintrc.js diff ../hello-world/.gitignore .gitignore + diff ../hello-world/ui-tests/playwright.config.ts ./ui-tests/playwright.config.ts + diff ../hello-world/ui-tests/README.md ./ui-tests/README.md shell: bash - name: Install node if: steps.filter.outputs.extension == 'true' @@ -103,13 +105,34 @@ jobs: - name: Build and check by extension if: steps.filter.outputs.extension == 'true' run: | - pip install . -vvv + pip install . -v jupyter labextension list 2>&1 | tee labextension.list cat labextension.list | grep -ie "@jupyterlab-examples/*.*OK" python -m jupyterlab.browser_check pip uninstall -y $(python setup.py --name) shell: bash + - name: Integration tests + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') + run: | + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down || true + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env pull -q || true + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e + - name: Upload UI Test artifacts + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') && always() + uses: actions/upload-artifact@v2 + with: + name: ui-test-output + path: | + ${{ matrix.example }}/ui-tests/test-results + - name: Stop containers + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') && always() + run: | + # Print jupyterlab logs before removing the containers using the container name set in docker-compose file + docker logs jupyterlab + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down + build_serverextension: runs-on: ${{ matrix.os }} strategy: @@ -147,6 +170,8 @@ jobs: diff hello-world/tsconfig.json server-extension/tsconfig.json diff hello-world/.eslintignore server-extension/.eslintignore diff hello-world/.eslintrc.js server-extension/.eslintrc.js + diff hello-world/ui-tests/playwright.config.ts server-extension/ui-tests/playwright.config.ts + diff hello-world/ui-tests/README.md server-extension/ui-tests/README.md shell: bash - name: Install Python if: steps.filter.outputs.extension == 'true' @@ -218,6 +243,29 @@ jobs: cat labextension.list | grep -ie "@jupyterlab-examples/server-extension.*OK" python -m jupyterlab.browser_check + - name: Integration tests + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') + run: | + cd server-extension + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down || true + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env pull -q || true + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env build + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e + - name: Upload UI Test artifacts + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') && always() + uses: actions/upload-artifact@v2 + with: + name: ui-test-output + path: | + server-extension/ui-tests/test-results + - name: Stop containers + if: steps.filter.outputs.extension == 'true' && startsWith(runner.os, 'Linux') && always() + run: | + cd server-extension + # Print jupyterlab logs before removing the containers using the container name set in docker-compose file + docker logs jupyterlab + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down + build_all: runs-on: ${{ matrix.os }} strategy: diff --git a/.gitignore b/.gitignore index a39dd1f9..8b43cc12 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ __pycache__ static labextension +**/test-results/ diff --git a/command-palette/.eslintignore b/command-palette/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/command-palette/.eslintignore +++ b/command-palette/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/command-palette/style/index.css b/command-palette/style/index.css index e69de29b..8a7ea29e 100644 --- a/command-palette/style/index.css +++ b/command-palette/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/command-palette/ui-tests/.env b/command-palette/ui-tests/.env new file mode 100644 index 00000000..b2c68144 --- /dev/null +++ b/command-palette/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=command-palette/jupyterlab_examples_command_palette +EXT_NAME=command-palette \ No newline at end of file diff --git a/command-palette/ui-tests/README.md b/command-palette/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/command-palette/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/command-palette/ui-tests/package.json b/command-palette/ui-tests/package.json new file mode 100644 index 00000000..3543d9d8 --- /dev/null +++ b/command-palette/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/command-palette-tests", + "version": "0.1.0", + "description": "Integration test for command-palette example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/command-palette/ui-tests/playwright.config.ts b/command-palette/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/command-palette/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/command-palette/ui-tests/tests/command-palette.spec.ts b/command-palette/ui-tests/tests/command-palette.spec.ts new file mode 100644 index 00000000..4ac74b63 --- /dev/null +++ b/command-palette/ui-tests/tests/command-palette.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit console message when called from palette', async ({ + page, +}) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=View + await page.click('text=View'); + // Click text=Activate Command Palette + await page.click('text=Activate Command Palette'); + // Fill [aria-label="Command Palette Section"] [placeholder="SEARCH"] + await page.fill( + '[aria-label="Command Palette Section"] [placeholder="SEARCH"]', + 'Execute' + ); + // Click text=Execute jlab-examples:command-palette Command + await page.click('text=Execute jlab-examples:command-palette Command'); + + expect( + logs.filter((s) => + s.startsWith('jlab-examples:command-palette has been called from palette') + ) + ).toHaveLength(1); +}); diff --git a/commands/.eslintignore b/commands/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/commands/.eslintignore +++ b/commands/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/commands/style/index.css b/commands/style/index.css index e69de29b..8a7ea29e 100644 --- a/commands/style/index.css +++ b/commands/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/commands/ui-tests/.env b/commands/ui-tests/.env new file mode 100644 index 00000000..04e2151c --- /dev/null +++ b/commands/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=commands/jupyterlab_examples_commands +EXT_NAME=commands \ No newline at end of file diff --git a/commands/ui-tests/README.md b/commands/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/commands/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/commands/ui-tests/package.json b/commands/ui-tests/package.json new file mode 100644 index 00000000..35a6e9c2 --- /dev/null +++ b/commands/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/commands-tests", + "version": "0.1.0", + "description": "Integration test for commands example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/commands/ui-tests/playwright.config.ts b/commands/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/commands/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/commands/ui-tests/tests/commands.spec.ts b/commands/ui-tests/tests/commands.spec.ts new file mode 100644 index 00000000..d6e34bd9 --- /dev/null +++ b/commands/ui-tests/tests/commands.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit console message', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + expect( + logs.filter((s) => s === 'jlab-examples:command has been called from init.') + ).toHaveLength(1); +}); diff --git a/context-menu/.eslintignore b/context-menu/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/context-menu/.eslintignore +++ b/context-menu/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/context-menu/ui-tests/.env b/context-menu/ui-tests/.env new file mode 100644 index 00000000..3277c390 --- /dev/null +++ b/context-menu/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=context-menu/jupyterlab_examples_context_menu +EXT_NAME=context-menu \ No newline at end of file diff --git a/context-menu/ui-tests/README.md b/context-menu/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/context-menu/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/context-menu/ui-tests/package.json b/context-menu/ui-tests/package.json new file mode 100644 index 00000000..a0920c7b --- /dev/null +++ b/context-menu/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/context-menu-tests", + "version": "0.1.0", + "description": "Integration test for context-menu example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/context-menu/ui-tests/playwright.config.ts b/context-menu/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/context-menu/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/context-menu/ui-tests/tests/context-menu.spec.ts b/context-menu/ui-tests/tests/context-menu.spec.ts new file mode 100644 index 00000000..a21a9834 --- /dev/null +++ b/context-menu/ui-tests/tests/context-menu.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should have new context menu for example files', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click li[role="menuitem"]:has-text("File") + await page.click('li[role="menuitem"]:has-text("File")'); + + // Click ul[role="menu"] >> text=New + await page.click('ul[role="menu"] >> text=New'); + + // Click #jp-mainmenu-file-new >> text=Text File + await page.click('#jp-mainmenu-file-new >> text=Text File'); + + // Click [aria-label="File Browser Section"] >> text=untitled.txt + await page.click('[aria-label="File Browser Section"] >> text=untitled.txt', { + button: 'right', + }); + + // Click text=Rename + await page.click('text=Rename'); + + // Fill file browser >> input + await page.fill('input.jp-DirListing-editor', 'test.example'); + + // Press Enter + await page.press('input.jp-DirListing-editor', 'Enter'); + + // Click [aria-label="File Browser Section"] >> text=test.example + await page.click('[aria-label="File Browser Section"] >> text=test.example', { + button: 'right', + }); + + // Click ul[role="menu"] >> text=Example + await page.click('ul[role="menu"] >> text=Example'); + + // Click text=Path: test.example + expect(await page.waitForSelector('text=Path: test.example')).toBeTruthy(); + + // Click button:has-text("OK") + await page.click('button:has-text("OK")'); +}); diff --git a/custom-log-console/.eslintignore b/custom-log-console/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/custom-log-console/.eslintignore +++ b/custom-log-console/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/custom-log-console/style/index.css b/custom-log-console/style/index.css index e69de29b..8a7ea29e 100644 --- a/custom-log-console/style/index.css +++ b/custom-log-console/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/custom-log-console/ui-tests/.env b/custom-log-console/ui-tests/.env new file mode 100644 index 00000000..4b2e358f --- /dev/null +++ b/custom-log-console/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=custom-log-console/jupyterlab_examples_custom_log_console +EXT_NAME=custom-log-console \ No newline at end of file diff --git a/custom-log-console/ui-tests/README.md b/custom-log-console/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/custom-log-console/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/custom-log-console/ui-tests/package.json b/custom-log-console/ui-tests/package.json new file mode 100644 index 00000000..9bf981b1 --- /dev/null +++ b/custom-log-console/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/custom-log-console-tests", + "version": "0.1.0", + "description": "Integration test for custom-log-console example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/custom-log-console/ui-tests/playwright.config.ts b/custom-log-console/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/custom-log-console/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/custom-log-console/ui-tests/tests/custom-log-console.spec.ts b/custom-log-console/ui-tests/tests/custom-log-console.spec.ts new file mode 100644 index 00000000..9c4ff104 --- /dev/null +++ b/custom-log-console/ui-tests/tests/custom-log-console.spec.ts @@ -0,0 +1,94 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a log panel and filter message depending on the log level.', async ({ + page, +}) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + + // Click ul[role="menu"] >> text=Custom Log Console + await page.click('ul[role="menu"] >> text=Custom Log Console'); + + // Click div[role="main"] >> text=Log: custom-log-console + expect( + await page.waitForSelector( + 'div[role="main"] >> text=Log: custom-log-console' + ) + ).toBeTruthy(); + + // Click text=Log Console Example menu + await page.click('text=Log Console Example'); + // Click text=HTML log message + await page.click('text=HTML log message'); + + expect(await page.waitForSelector('text=Hello world HTML!!')).toBeTruthy(); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + // Click text=Text log message + await page.click('text=Text log message'); + + expect(await page.waitForSelector('text=Hello world text!!')).toBeTruthy(); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + // Click text=Output log message + await page.click('text=Output log message'); + + expect( + await page.waitForSelector('text=Hello world nbformat!!') + ).toBeTruthy(); + + // Click button:has-text("Add Checkpoint") + await page.click('button:has-text("Add Checkpoint")'); + + expect(await page.waitForSelector('hr')).toBeTruthy(); + + await page.click('button:has-text("Clear Log")'); + await page.waitForSelector('text=No log messages.'); + + // Select warning + await page.selectOption('[aria-label="Log level"]', 'warning'); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + // Click text=Output log message + await page.click('text=Output log message'); + + expect( + await page.waitForSelector('text=Hello world nbformat!!') + ).toBeTruthy(); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + // Click text=HTML log message + await page.click('text=HTML log message'); + + let failed = true; + try { + await page.waitForSelector('text=Hello world HTML!!', { + state: 'attached', + timeout: 200, + }); + } catch (e) { + failed = false; + expect(e).toBeTruthy(); + } + expect(failed).toBe(false); + + // Select debug + await page.selectOption('[aria-label="Log level"]', 'debug'); + + // Click text=Log Console Example + await page.click('text=Log Console Example'); + // Click text=HTML log message + await page.click('text=HTML log message'); + + expect(await page.waitForSelector('text=Hello world HTML!!')).toBeTruthy(); +}); diff --git a/datagrid/.eslintignore b/datagrid/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/datagrid/.eslintignore +++ b/datagrid/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/datagrid/style/base.css b/datagrid/style/base.css index e69de29b..9896b5ef 100644 --- a/datagrid/style/base.css +++ b/datagrid/style/base.css @@ -0,0 +1,3 @@ +.jp-tutorial-view { + background-color: AliceBlue; +} diff --git a/datagrid/style/index.css b/datagrid/style/index.css index 9896b5ef..8a7ea29e 100644 --- a/datagrid/style/index.css +++ b/datagrid/style/index.css @@ -1,3 +1 @@ -.jp-tutorial-view { - background-color: AliceBlue; -} +@import url('base.css'); diff --git a/datagrid/ui-tests/.env b/datagrid/ui-tests/.env new file mode 100644 index 00000000..6acc6cbf --- /dev/null +++ b/datagrid/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=datagrid/jupyterlab_examples_datagrid +EXT_NAME=datagrid \ No newline at end of file diff --git a/datagrid/ui-tests/README.md b/datagrid/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/datagrid/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/datagrid/ui-tests/package.json b/datagrid/ui-tests/package.json new file mode 100644 index 00000000..314414f1 --- /dev/null +++ b/datagrid/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/datagrid-tests", + "version": "0.1.0", + "description": "Integration test for datagrid example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/datagrid/ui-tests/playwright.config.ts b/datagrid/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/datagrid/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/datagrid/ui-tests/tests/datagrid.spec.ts b/datagrid/ui-tests/tests/datagrid.spec.ts new file mode 100644 index 00000000..14a52e47 --- /dev/null +++ b/datagrid/ui-tests/tests/datagrid.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a datagrid panel', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + // Click text=DataGrid Example + await page.click('text=DataGrid Example'); + // Click ul[role="menu"] >> text=Open a Datagrid + await page.click('ul[role="menu"] >> text=Open a Datagrid'); + + expect( + await page.waitForSelector('div[role="main"] >> text=Datagrid Example View') + ).toBeTruthy(); + + // Compare screenshot with a stored reference. + expect(await page.screenshot()).toMatchSnapshot('datagrid-example.png'); +}); diff --git a/datagrid/ui-tests/tests/datagrid.spec.ts-snapshots/datagrid-example-chromium-linux.png b/datagrid/ui-tests/tests/datagrid.spec.ts-snapshots/datagrid-example-chromium-linux.png new file mode 100644 index 00000000..2a8f2d2b Binary files /dev/null and b/datagrid/ui-tests/tests/datagrid.spec.ts-snapshots/datagrid-example-chromium-linux.png differ diff --git a/end-to-end-tests/Dockerfile b/end-to-end-tests/Dockerfile new file mode 100644 index 00000000..5ee47de9 --- /dev/null +++ b/end-to-end-tests/Dockerfile @@ -0,0 +1,9 @@ +# Use base jupyter image that comes with jupyterlab +FROM jupyter/base-notebook + +USER root + +# Upgrade JupyterLab +RUN python -m pip install --pre --upgrade jupyterlab + +USER 1000 diff --git a/end-to-end-tests/docker-compose.yml b/end-to-end-tests/docker-compose.yml new file mode 100644 index 00000000..be500a9e --- /dev/null +++ b/end-to-end-tests/docker-compose.yml @@ -0,0 +1,59 @@ +version: '3.5' + +services: + lab: + container_name: jupyterlab + build: + context: . + command: [ + 'jupyter', + 'lab', + # Set working directory as empty directory - no modified time displayed + '--notebook-dir=./work', + '--ServerApp.token=', + '--ServerApp.password=', + '--ServerApp.disable_check_xsrf=True', + '--LabServerApp.extra_labextensions_path=/opt/labextension', + # Workaround bug: https://github.com/ipython/traitlets/issues/668 + '--LabServerApp.extra_labextensions_path=/dev/null', + ] + environment: + PYTHONPATH: /opt/serverextension + networks: + - frontend + ports: + - 8888:8888 + volumes: + # Extension folder and name are set externally through .env file + - ../$EXT_FOLDER/labextension:/opt/labextension/@jupyterlab-examples/$EXT_NAME + - ../$EXT_FOLDER/..:/opt/serverextension + - ../$EXT_FOLDER/../jupyter-config/server-config:/etc/jupyter/jupyter_server_config.d + + e2e: + # docker must match playwright version in ui-tests/package.json + image: mcr.microsoft.com/playwright:v1.12.3-focal + entrypoint: + [ + '/tmp/e2e-tests/prepare.sh', + 'jupyterlab:8888', + '--strict', + '--timeout=60', + '--', + ] + command: ['npx', 'playwright', 'test'] + environment: + # JupyterLab URL + TARGET_URL: http://jupyterlab:8888 + # See https://playwright.dev/docs/docker/#run-the-image + ipc: host + networks: + - frontend + depends_on: + - lab + volumes: + - .:/tmp/e2e-tests + - ../$EXT_FOLDER/../ui-tests:/opt/ui-tests + working_dir: /opt/ui-tests + +networks: + frontend: diff --git a/end-to-end-tests/prepare.sh b/end-to-end-tests/prepare.sh new file mode 100755 index 00000000..3e8e4f2d --- /dev/null +++ b/end-to-end-tests/prepare.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +yarn install +echo Will run 'wait-for-it' $* +$(dirname $0)/wait-for-it.sh $* diff --git a/end-to-end-tests/wait-for-it.sh b/end-to-end-tests/wait-for-it.sh new file mode 100755 index 00000000..d990e0d3 --- /dev/null +++ b/end-to-end-tests/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/hello-world/.eslintignore b/hello-world/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/hello-world/.eslintignore +++ b/hello-world/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/hello-world/style/index.css b/hello-world/style/index.css index e69de29b..8a7ea29e 100644 --- a/hello-world/style/index.css +++ b/hello-world/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/hello-world/ui-tests/.env b/hello-world/ui-tests/.env new file mode 100644 index 00000000..dad7414f --- /dev/null +++ b/hello-world/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=hello-world/jupyterlab_examples_hello_world +EXT_NAME=hello-world \ No newline at end of file diff --git a/hello-world/ui-tests/README.md b/hello-world/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/hello-world/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/hello-world/ui-tests/package.json b/hello-world/ui-tests/package.json new file mode 100644 index 00000000..e5c2721e --- /dev/null +++ b/hello-world/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/hello-world-tests", + "version": "0.1.0", + "description": "Integration test for hello-world example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/hello-world/ui-tests/playwright.config.ts b/hello-world/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/hello-world/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/hello-world/ui-tests/tests/hello-world.spec.ts b/hello-world/ui-tests/tests/hello-world.spec.ts new file mode 100644 index 00000000..ecc37733 --- /dev/null +++ b/hello-world/ui-tests/tests/hello-world.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit console message', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + expect( + logs.filter((s) => s.startsWith('the JupyterLab main application')) + ).toHaveLength(1); +}); diff --git a/kernel-messaging/.eslintignore b/kernel-messaging/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/kernel-messaging/.eslintignore +++ b/kernel-messaging/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/kernel-messaging/style/base.css b/kernel-messaging/style/base.css index e69de29b..d703e460 100644 --- a/kernel-messaging/style/base.css +++ b/kernel-messaging/style/base.css @@ -0,0 +1,16 @@ +.jp-example-view { + background-color: AliceBlue; +} + +.jp-example-button { + background-color: red; + border-radius: 12px; + border: none; + color: white; + padding: 15px 32px; + margin: 30px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} diff --git a/kernel-messaging/style/index.css b/kernel-messaging/style/index.css index d703e460..8a7ea29e 100644 --- a/kernel-messaging/style/index.css +++ b/kernel-messaging/style/index.css @@ -1,16 +1 @@ -.jp-example-view { - background-color: AliceBlue; -} - -.jp-example-button { - background-color: red; - border-radius: 12px; - border: none; - color: white; - padding: 15px 32px; - margin: 30px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} +@import url('base.css'); diff --git a/kernel-messaging/ui-tests/.env b/kernel-messaging/ui-tests/.env new file mode 100644 index 00000000..f9b5c34e --- /dev/null +++ b/kernel-messaging/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=kernel-messaging/jupyterlab_examples_kernel_messaging +EXT_NAME=kernel-messaging \ No newline at end of file diff --git a/kernel-messaging/ui-tests/README.md b/kernel-messaging/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/kernel-messaging/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/kernel-messaging/ui-tests/package.json b/kernel-messaging/ui-tests/package.json new file mode 100644 index 00000000..42aea317 --- /dev/null +++ b/kernel-messaging/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/kernel-messaging-tests", + "version": "0.1.0", + "description": "Integration test for kernel-messaging example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/kernel-messaging/ui-tests/playwright.config.ts b/kernel-messaging/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/kernel-messaging/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts b/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts new file mode 100644 index 00000000..5af13ec6 --- /dev/null +++ b/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a panel connected to a kernel', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=Kernel Messaging + await page.click('text=Kernel Messaging'); + + // Click ul[role="menu"] >> text=Open the Kernel Messaging Panel + await page.click('ul[role="menu"] >> text=Open the Kernel Messaging Panel'); + + // Click button:has-text("Select") + await page.click('button:has-text("Select")'); + + // Click text=Compute 3+5 + await page.click('text=Compute 3+5'); + + // wait for text=/.*\{"data":\{"text/plain":"8"\},"metadata":\{\},"execution_count":1\}.*/ + expect( + await page.waitForSelector( + 'text=/.*"data":{"text/plain":"8"},"metadata":{}.*/' + ) + ).toBeTruthy(); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + // Compare screenshot with a stored reference. + expect(await page.screenshot()).toMatchSnapshot( + 'kernel-messaging-example.png' + ); +}); diff --git a/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts-snapshots/kernel-messaging-example-chromium-linux.png b/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts-snapshots/kernel-messaging-example-chromium-linux.png new file mode 100644 index 00000000..77816318 Binary files /dev/null and b/kernel-messaging/ui-tests/tests/kernel-messaging.spec.ts-snapshots/kernel-messaging-example-chromium-linux.png differ diff --git a/kernel-output/.eslintignore b/kernel-output/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/kernel-output/.eslintignore +++ b/kernel-output/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/kernel-output/style/base.css b/kernel-output/style/base.css index e69de29b..d703e460 100644 --- a/kernel-output/style/base.css +++ b/kernel-output/style/base.css @@ -0,0 +1,16 @@ +.jp-example-view { + background-color: AliceBlue; +} + +.jp-example-button { + background-color: red; + border-radius: 12px; + border: none; + color: white; + padding: 15px 32px; + margin: 30px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} diff --git a/kernel-output/style/index.css b/kernel-output/style/index.css index d703e460..8a7ea29e 100644 --- a/kernel-output/style/index.css +++ b/kernel-output/style/index.css @@ -1,16 +1 @@ -.jp-example-view { - background-color: AliceBlue; -} - -.jp-example-button { - background-color: red; - border-radius: 12px; - border: none; - color: white; - padding: 15px 32px; - margin: 30px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} +@import url('base.css'); diff --git a/kernel-output/ui-tests/.env b/kernel-output/ui-tests/.env new file mode 100644 index 00000000..54ff1afa --- /dev/null +++ b/kernel-output/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=kernel-output/jupyterlab_examples_kernel_output +EXT_NAME=kernel-output \ No newline at end of file diff --git a/kernel-output/ui-tests/README.md b/kernel-output/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/kernel-output/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/kernel-output/ui-tests/package.json b/kernel-output/ui-tests/package.json new file mode 100644 index 00000000..cc8b8c7a --- /dev/null +++ b/kernel-output/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/kernel-output-tests", + "version": "0.1.0", + "description": "Integration test for kernel-output example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/kernel-output/ui-tests/playwright.config.ts b/kernel-output/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/kernel-output/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/kernel-output/ui-tests/tests/kernel-output.spec.ts b/kernel-output/ui-tests/tests/kernel-output.spec.ts new file mode 100644 index 00000000..c969814d --- /dev/null +++ b/kernel-output/ui-tests/tests/kernel-output.spec.ts @@ -0,0 +1,107 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a panel connected to a notebook kernel', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Install pandas through console + await page.click('text=File'); + await page.click('ul[role="menu"] >> text=New'); + await page.click('#jp-mainmenu-file-new >> text=Console'); + await page.click('button:has-text("Select")'); + + await page.fill( + 'text=[ ]:xxxxxxxxxx ​ >> textarea', + '!mamba install -qy pandas' + ); + await page.click('text=Run'); + await page.click('ul[role="menu"] >> text=Run Cell'); + await page.waitForSelector('text=Executing transaction: ...working... done'); + + // Click text=File + await page.click('text=File'); + + // Click ul[role="menu"] >> text=New + await page.click('ul[role="menu"] >> text=New'); + + // Click #jp-mainmenu-file-new >> text=Notebook + await page.click('#jp-mainmenu-file-new >> text=Notebook'); + + // Click button:has-text("Select") + await page.click('button:has-text("Select")'); + + // Click text=eyes + await page.fill( + 'text=[ ]: ​ >> textarea', + 'import numpy\nimport pandas\ndf = pandas.DataFrame(numpy.eye(5))' + ); + + // Click text=Run + await page.click('text=Run'); + + // Click Run selected cell + await page.click('ul[role="menu"] >> text=Run Selected Cells'); + + // Click text=Kernel Output + await page.click('text=Kernel Output'); + + // Click ul[role="menu"] >> text=Open the Kernel Output Panel + await page.click('ul[role="menu"] >> text=Open the Kernel Output Panel'); + + // Select Notebook kernel + await page.selectOption('.jp-Dialog-body >> select', { + label: 'Untitled.ipynb', + }); + + // Click button:has-text("Select") + await page.click('button:has-text("Select")'); + + // Emulate drag and drop to place the panel next to the notebook + const viewerHandle = await page.$( + 'div[role="main"] >> text=Kernel Output Example View' + ); + const panelHandle = await page.$('#kernel-output-panel div'); + const viewerBBox = await viewerHandle.boundingBox(); + const panelBBox = await panelHandle.boundingBox(); + + await page.mouse.move( + viewerBBox.x + 0.5 * viewerBBox.width, + viewerBBox.y + 0.5 * viewerBBox.height + ); + await page.mouse.down(); + await page.mouse.move( + panelBBox.x + 0.8 * panelBBox.width, + panelBBox.y + 0.5 * panelBBox.height + ); + await page.mouse.up(); + + // Click div[role="main"] >> text=Kernel Output Example View + await page.click('div[role="main"] >> text=Kernel Output Example View'); + + // Click text=Kernel Output + await page.click('text=Kernel Output'); + + // Click ul[role="menu"] >> text=Contact Kernel and Execute Code + await page.click('ul[role="menu"] >> text=Contact Kernel and Execute Code'); + + // Fill [placeholder="Statement to execute"] + await page.fill('[placeholder="Statement to execute"]', 'df'); + + // Click button:has-text("Execute") + await page.click('button:has-text("Execute")'); + + expect(await page.waitForSelector('th')).toBeTruthy(); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + // Compare screenshot with a stored reference. + expect(await page.screenshot()).toMatchSnapshot('kernel-output-example.png'); +}); diff --git a/kernel-output/ui-tests/tests/kernel-output.spec.ts-snapshots/kernel-output-example-chromium-linux.png b/kernel-output/ui-tests/tests/kernel-output.spec.ts-snapshots/kernel-output-example-chromium-linux.png new file mode 100644 index 00000000..dbac6d57 Binary files /dev/null and b/kernel-output/ui-tests/tests/kernel-output.spec.ts-snapshots/kernel-output-example-chromium-linux.png differ diff --git a/launcher/.eslintignore b/launcher/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/launcher/.eslintignore +++ b/launcher/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/launcher/style/index.css b/launcher/style/index.css index e69de29b..8a7ea29e 100644 --- a/launcher/style/index.css +++ b/launcher/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/launcher/ui-tests/.env b/launcher/ui-tests/.env new file mode 100644 index 00000000..f224d04c --- /dev/null +++ b/launcher/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=launcher/jupyterlab_examples_launcher +EXT_NAME=launcher \ No newline at end of file diff --git a/launcher/ui-tests/README.md b/launcher/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/launcher/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/launcher/ui-tests/package.json b/launcher/ui-tests/package.json new file mode 100644 index 00000000..8ff7ad13 --- /dev/null +++ b/launcher/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/launcher-tests", + "version": "0.1.0", + "description": "Integration test for launcher example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/launcher/ui-tests/playwright.config.ts b/launcher/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/launcher/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/launcher/ui-tests/tests/launcher.spec.ts b/launcher/ui-tests/tests/launcher.spec.ts new file mode 100644 index 00000000..7ecb487d --- /dev/null +++ b/launcher/ui-tests/tests/launcher.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should add a card to create Python file', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Scroll to the new card + await page.focus('text=Extension ExamplesPython File >> p'); + + await page.click('text=Extension ExamplesPython File >> p'); + + // Click div[role="main"] >> text=untitled.py + await page.click('div[role="main"] >> text=untitled.py'); +}); diff --git a/log-messages/.eslintignore b/log-messages/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/log-messages/.eslintignore +++ b/log-messages/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/log-messages/style/index.css b/log-messages/style/index.css index e69de29b..8a7ea29e 100644 --- a/log-messages/style/index.css +++ b/log-messages/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/log-messages/ui-tests/.env b/log-messages/ui-tests/.env new file mode 100644 index 00000000..eced602c --- /dev/null +++ b/log-messages/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=log-messages/jupyterlab_examples_log_messages +EXT_NAME=log-messages \ No newline at end of file diff --git a/log-messages/ui-tests/README.md b/log-messages/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/log-messages/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/log-messages/ui-tests/package.json b/log-messages/ui-tests/package.json new file mode 100644 index 00000000..b7672318 --- /dev/null +++ b/log-messages/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/log-messages-tests", + "version": "0.1.0", + "description": "Integration test for log-messages example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/log-messages/ui-tests/playwright.config.ts b/log-messages/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/log-messages/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/log-messages/ui-tests/tests/log-messages.spec.ts b/log-messages/ui-tests/tests/log-messages.spec.ts new file mode 100644 index 00000000..08b528a7 --- /dev/null +++ b/log-messages/ui-tests/tests/log-messages.spec.ts @@ -0,0 +1,73 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should capture log messages in dedicated panel', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click File menu + await page.click('text=File'); + // Click ul[role="menu"] >> text=New + await page.click('ul[role="menu"] >> text=New'); + // Click #jp-mainmenu-file-new >> text=Notebook + await page.click('#jp-mainmenu-file-new >> text=Notebook'); + // Click button:has-text("Select") + await page.click('button:has-text("Select")'); + + // Click text=View + await page.click('text=View'); + // Click ul[role="menu"] >> text=Show Log Console + await page.click('ul[role="menu"] >> text=Show Log Console'); + + // Drag and drop the split to display a bigger log panel. + const splitHandle = await page.$('div.lm-SplitPanel-handle'); + const splitHandleBBox = await splitHandle.boundingBox(); + await page.mouse.move( + splitHandleBBox.x + 0.5 * splitHandleBBox.width, + splitHandleBBox.y + 0.5 + splitHandleBBox.height + ); + await page.mouse.down(); + await page.mouse.move( + splitHandleBBox.x + 0.5 * splitHandleBBox.width, + splitHandleBBox.y + 0.5 + splitHandleBBox.height - 200 + ); + await page.mouse.up(); + + // Click text=Log Messages Example + await page.click('text=Log Messages Example'); + // Click text=Text log message + await page.click('text=Text log message'); + + let failed = true; + try { + await page.waitForSelector('text=Hello world text!!', { timeout: 200 }); + } catch (e) { + failed = false; + expect(e).toBeTruthy(); + } + expect(failed).toBe(false); + + // Select debug + await page.selectOption('[aria-label="Log level"]', 'debug'); + // Click text=Log Messages Example + await page.click('text=Log Messages Example'); + // Click text=Text log message + await page.click('text=Text log message'); + + expect(await page.waitForSelector('text=Hello world text!!')).toBeTruthy(); + + // Click button:has-text("Clear Log") + await page.click('button:has-text("Clear Log")'); + + failed = true; + try { + await page.waitForSelector('text=Hello world text!!', { timeout: 200 }); + throw new Error('Log messages were not cleared.'); + } catch (e) { + failed = false; + expect(e).toBeTruthy(); + } + expect(failed).toBe(false); +}); diff --git a/main-menu/.eslintignore b/main-menu/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/main-menu/.eslintignore +++ b/main-menu/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/main-menu/style/index.css b/main-menu/style/index.css index e69de29b..8a7ea29e 100644 --- a/main-menu/style/index.css +++ b/main-menu/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/main-menu/ui-tests/.env b/main-menu/ui-tests/.env new file mode 100644 index 00000000..93354cc9 --- /dev/null +++ b/main-menu/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=main-menu/jupyterlab_examples_main_menu +EXT_NAME=main-menu \ No newline at end of file diff --git a/main-menu/ui-tests/README.md b/main-menu/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/main-menu/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/main-menu/ui-tests/package.json b/main-menu/ui-tests/package.json new file mode 100644 index 00000000..5f464a38 --- /dev/null +++ b/main-menu/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/main-menu-tests", + "version": "0.1.0", + "description": "Integration test for main-menu example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/main-menu/ui-tests/playwright.config.ts b/main-menu/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/main-menu/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/main-menu/ui-tests/tests/main-menu.spec.ts b/main-menu/ui-tests/tests/main-menu.spec.ts new file mode 100644 index 00000000..b1a2eb2f --- /dev/null +++ b/main-menu/ui-tests/tests/main-menu.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit a message in a dialog when menu is triggered', async ({ + page, +}) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=Main Menu Example + await page.click('text=Main Menu Example'); + + // Add listener to check alert message + // > Alert are not capture by the recording + page.once('dialog', async (dialog) => { + expect(dialog.message()).toEqual( + 'jlab-examples:main-menu has been called from the menu.' + ); + + dialog.dismiss().catch(() => {}); + }); + + // Click ul[role="menu"] >> text=Execute jlab-examples:main-menu Command + await page.click( + 'ul[role="menu"] >> text=Execute jlab-examples:main-menu Command' + ); + + expect( + logs.filter( + (s) => s === 'jlab-examples:main-menu has been called from the menu.' + ) + ).toHaveLength(1); +}); diff --git a/react-widget/.eslintignore b/react-widget/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/react-widget/.eslintignore +++ b/react-widget/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/react-widget/style/base.css b/react-widget/style/base.css index e69de29b..d8657f4c 100644 --- a/react-widget/style/base.css +++ b/react-widget/style/base.css @@ -0,0 +1,9 @@ +.jp-ReactWidget { + color: var(--jp-ui-font-color1); + background: var(--jp-layout-color1); + font-size: 48px; + display: flex; + align-items: center; + justify-content: center; + text-align: center; +} diff --git a/react-widget/style/index.css b/react-widget/style/index.css index d8657f4c..8a7ea29e 100644 --- a/react-widget/style/index.css +++ b/react-widget/style/index.css @@ -1,9 +1 @@ -.jp-ReactWidget { - color: var(--jp-ui-font-color1); - background: var(--jp-layout-color1); - font-size: 48px; - display: flex; - align-items: center; - justify-content: center; - text-align: center; -} +@import url('base.css'); diff --git a/react-widget/ui-tests/.env b/react-widget/ui-tests/.env new file mode 100644 index 00000000..0e0d33e4 --- /dev/null +++ b/react-widget/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=react-widget/jupyterlab_examples_react_widget +EXT_NAME=react-widget \ No newline at end of file diff --git a/react-widget/ui-tests/README.md b/react-widget/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/react-widget/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/react-widget/ui-tests/package.json b/react-widget/ui-tests/package.json new file mode 100644 index 00000000..6a78f421 --- /dev/null +++ b/react-widget/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/react-widget-tests", + "version": "0.1.0", + "description": "Integration test for react-widget example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/react-widget/ui-tests/playwright.config.ts b/react-widget/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/react-widget/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/react-widget/ui-tests/tests/react-widget.spec.ts b/react-widget/ui-tests/tests/react-widget.spec.ts new file mode 100644 index 00000000..614ae2dc --- /dev/null +++ b/react-widget/ui-tests/tests/react-widget.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a new panel with a react component', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=React Widget + await page.click('text=React Widget'); + + // Click text=You clicked 0 times! + expect(await page.waitForSelector('text=You clicked 0 times!')).toBeTruthy(); + + // Click text=Increment + await page.click('text=Increment'); + // Click text=Increment + await page.click('text=Increment'); + // Click text=Increment + await page.click('text=Increment'); + // Click text=Increment + await page.click('text=Increment'); + + // Click text=You clicked 4 times! + expect(await page.waitForSelector('text=You clicked 4 times!')).toBeTruthy(); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + expect(await page.screenshot()).toMatchSnapshot('react-widget-example.png'); +}); diff --git a/react-widget/ui-tests/tests/react-widget.spec.ts-snapshots/react-widget-example-chromium-linux.png b/react-widget/ui-tests/tests/react-widget.spec.ts-snapshots/react-widget-example-chromium-linux.png new file mode 100644 index 00000000..c6f1f894 Binary files /dev/null and b/react-widget/ui-tests/tests/react-widget.spec.ts-snapshots/react-widget-example-chromium-linux.png differ diff --git a/scripts/run_all_ui_tests.sh b/scripts/run_all_ui_tests.sh new file mode 100755 index 00000000..557871be --- /dev/null +++ b/scripts/run_all_ui_tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e +HERE=$(dirname $0) +# Run the passed argument; fallback to `npx playwright test` +## To update all screenshots run ./scripts/run_all_ui_tests.sh npx playwright test -u +COMMAND=${*:-npx playwright test} + +declare -a extensions=("command-palette" "commands" "context-menu" "custom-log-console" "datagrid" "hello-world" "kernel-messaging" "kernel-output" "launcher" "log-messages" "main-menu" "react-widget" "settings" "server-extension" "signals" "state" "toolbar-button" "widgets") + +# Build docker image +docker-compose -f $HERE/../end-to-end-tests/docker-compose.yml build --no-cache + +for ext in ${extensions[@]}; do + echo Run \`$COMMAND\` on \'$ext\' + pushd $HERE/../$ext + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env run --rm e2e $COMMAND || true + docker-compose -f ../end-to-end-tests/docker-compose.yml --env-file ./ui-tests/.env down + popd +done diff --git a/server-extension/.eslintignore b/server-extension/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/server-extension/.eslintignore +++ b/server-extension/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/server-extension/style/index.css b/server-extension/style/index.css index e69de29b..8a7ea29e 100644 --- a/server-extension/style/index.css +++ b/server-extension/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/server-extension/ui-tests/.env b/server-extension/ui-tests/.env new file mode 100644 index 00000000..5a6b3bf8 --- /dev/null +++ b/server-extension/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=server-extension/jlab_ext_example +EXT_NAME=server-extension \ No newline at end of file diff --git a/server-extension/ui-tests/README.md b/server-extension/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/server-extension/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/server-extension/ui-tests/package.json b/server-extension/ui-tests/package.json new file mode 100644 index 00000000..dd3ccb8e --- /dev/null +++ b/server-extension/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/server-extension-tests", + "version": "0.1.0", + "description": "Integration test for server-extension example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/server-extension/ui-tests/playwright.config.ts b/server-extension/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/server-extension/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/server-extension/ui-tests/tests/server-extension.spec.ts b/server-extension/ui-tests/tests/server-extension.spec.ts new file mode 100644 index 00000000..afa8cd6f --- /dev/null +++ b/server-extension/ui-tests/tests/server-extension.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should store state between reloads', async ({ page }) => { + await Promise.all([ + page.waitForRequest( + (request) => + request.url().search(/jlab-ext-example\/hello/) >= 0 && + request.method() === 'GET' + ), + page.waitForRequest( + (request) => + request.url().search(/jlab-ext-example\/hello/) >= 0 && + request.method() === 'POST' && + request.postDataJSON()?.name === 'George' + ), + page.goto(`${TARGET_URL}/lab`), + ]); + + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + await page + .waitForSelector('text=Get Server Content in a IFrame Widget') + .then((h) => h.scrollIntoViewIfNeeded()); + + // Click text=Get Server Content in a IFrame Widget + await page.click('text=Get Server Content in a IFrame Widget'); + + // Wait for div[role="main"] >> text=Server Doc + await page.waitForSelector('div[role="main"] >> text=Server Doc'); + + expect( + await page + .frame({ url: /\/jlab-ext-example\/public\/index.html/ }) + .waitForSelector( + 'text=This content is served from the jlab_ext_example server extension.' + ) + ).toBeTruthy(); +}); diff --git a/settings/.eslintignore b/settings/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/settings/.eslintignore +++ b/settings/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/settings/style/index.css b/settings/style/index.css index e69de29b..8a7ea29e 100644 --- a/settings/style/index.css +++ b/settings/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/settings/ui-tests/.env b/settings/ui-tests/.env new file mode 100644 index 00000000..2e71caf1 --- /dev/null +++ b/settings/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=settings/jupyterlab_examples_settings +EXT_NAME=settings \ No newline at end of file diff --git a/settings/ui-tests/README.md b/settings/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/settings/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/settings/ui-tests/package.json b/settings/ui-tests/package.json new file mode 100644 index 00000000..4aa28cf0 --- /dev/null +++ b/settings/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/settings-tests", + "version": "0.1.0", + "description": "Integration test for settings example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/settings/ui-tests/playwright.config.ts b/settings/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/settings/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/settings/ui-tests/tests/settings.spec.ts b/settings/ui-tests/tests/settings.spec.ts new file mode 100644 index 00000000..0a3f7425 --- /dev/null +++ b/settings/ui-tests/tests/settings.spec.ts @@ -0,0 +1,75 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit console message', async ({ page }) => { + const logs: string[] = []; + + page.on('console', (message) => { + logs.push(message.text()); + }); + + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + expect( + logs.filter( + (s) => + s === + "Settings Example extension: Limit is set to '25' and flag to 'false'" + ) + ).toHaveLength(1); + + // Click :nth-match(:text("Settings"), 2) + await page.click(':nth-match(:text("Settings"), 2)'); + + // Click ul[role="menu"] >> text=Advanced Settings Editor + await page.click('ul[role="menu"] >> text=Advanced Settings Editor'); + + // Click span:has-text("Settings Example") + await page.click('span:has-text("Settings Example")'); + + // Click li[role="menuitem"]:has-text("Settings Example") + await page.click('li[role="menuitem"]:has-text("Settings Example")'); + + page.once('console', (message) => { + expect(message.text()).toEqual( + "Settings Example extension: Limit is set to '26' and flag to 'true'" + ); + }); + + // Click text=Toggle Flag and Increment Limit + page.once('dialog', (dialog) => { + expect(dialog.message()).toEqual( + "Settings Example extension: Limit is set to '26' and flag to 'true'" + ); + dialog.dismiss().catch(() => {}); + }); + await page.click('text=Toggle Flag and Increment Limit'); + + // Click li[role="menuitem"]:has-text("Settings Example") + await page.click('li[role="menuitem"]:has-text("Settings Example")'); + expect(await page.waitForSelector('ul[role="menu"] svg')).toBeTruthy(); + + // Double click text=26 + await page.dblclick('text=26'); + // codemirror edition requires to go through keyboard event + await page.keyboard.type('27'); + + page.once('console', (message) => { + expect(message.text()).toEqual( + "Settings Example extension: Limit is set to '27' and flag to 'true'" + ); + }); + + // Click text=Toggle Flag and Increment Limit + page.once('dialog', (dialog) => { + expect(dialog.message()).toEqual( + "Settings Example extension: Limit is set to '27' and flag to 'true'" + ); + dialog.dismiss().catch(() => {}); + }); + // Click button:has-text("Save User Settings") + await page.click('button:has-text("Save User Settings")'); +}); diff --git a/signals/.eslintignore b/signals/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/signals/.eslintignore +++ b/signals/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/signals/style/base.css b/signals/style/base.css index e69de29b..52582a1d 100644 --- a/signals/style/base.css +++ b/signals/style/base.css @@ -0,0 +1,16 @@ +.jp-SignalExamplePanel { + background-color: AliceBlue; +} + +.jp-ButtonWidget { + background-color: red; + border-radius: 12px; + border: none; + color: white; + padding: 15px 32px; + margin: 30px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; +} diff --git a/signals/style/index.css b/signals/style/index.css index 52582a1d..8a7ea29e 100644 --- a/signals/style/index.css +++ b/signals/style/index.css @@ -1,16 +1 @@ -.jp-SignalExamplePanel { - background-color: AliceBlue; -} - -.jp-ButtonWidget { - background-color: red; - border-radius: 12px; - border: none; - color: white; - padding: 15px 32px; - margin: 30px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} +@import url('base.css'); diff --git a/signals/ui-tests/.env b/signals/ui-tests/.env new file mode 100644 index 00000000..82239508 --- /dev/null +++ b/signals/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=signals/jupyterlab_examples_signals +EXT_NAME=signals \ No newline at end of file diff --git a/signals/ui-tests/README.md b/signals/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/signals/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/signals/ui-tests/package.json b/signals/ui-tests/package.json new file mode 100644 index 00000000..cd376542 --- /dev/null +++ b/signals/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/signals-tests", + "version": "0.1.0", + "description": "Integration test for signals example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/signals/ui-tests/playwright.config.ts b/signals/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/signals/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/signals/ui-tests/tests/signals.spec.ts b/signals/ui-tests/tests/signals.spec.ts new file mode 100644 index 00000000..95e35996 --- /dev/null +++ b/signals/ui-tests/tests/signals.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should emit console message and alert when button is pressed', async ({ + page, +}) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=Signal Example + await page.click('text=Signal Example'); + + // Click ul[role="menu"] >> text=Open the Signal Example Panel + await page.click('ul[role="menu"] >> text=Open the Signal Example Panel'); + + // Click text=Click me + page.once('console', (message) => { + expect( + message.text().startsWith('Hey, a Signal has been received from') + ).toEqual(true); + }); + page.once('dialog', (dialog) => { + expect(dialog.message()).toEqual( + 'The big red button has been clicked 1 times.' + ); + dialog.dismiss().catch(() => {}); + }); + await page.click('text=Click me'); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + expect(await page.screenshot()).toMatchSnapshot('signals-example.png'); +}); diff --git a/signals/ui-tests/tests/signals.spec.ts-snapshots/signals-example-chromium-linux.png b/signals/ui-tests/tests/signals.spec.ts-snapshots/signals-example-chromium-linux.png new file mode 100644 index 00000000..255777f6 Binary files /dev/null and b/signals/ui-tests/tests/signals.spec.ts-snapshots/signals-example-chromium-linux.png differ diff --git a/state/.eslintignore b/state/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/state/.eslintignore +++ b/state/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/state/style/index.css b/state/style/index.css index e69de29b..8a7ea29e 100644 --- a/state/style/index.css +++ b/state/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/state/ui-tests/.env b/state/ui-tests/.env new file mode 100644 index 00000000..ef742515 --- /dev/null +++ b/state/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=state/jupyterlab_examples_state +EXT_NAME=state \ No newline at end of file diff --git a/state/ui-tests/README.md b/state/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/state/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/state/ui-tests/package.json b/state/ui-tests/package.json new file mode 100644 index 00000000..c69ab00f --- /dev/null +++ b/state/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/state-tests", + "version": "0.1.0", + "description": "Integration test for state example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/state/ui-tests/playwright.config.ts b/state/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/state/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/state/ui-tests/tests/state.spec.ts b/state/ui-tests/tests/state.spec.ts new file mode 100644 index 00000000..a0f98737 --- /dev/null +++ b/state/ui-tests/tests/state.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should store state between reloads', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Check select current value + expect( + await page.$eval( + 'text=Pick an option to persist by the State Example extensiononetwothreeCancelOK >> select', + (s) => s.value + ) + ).toEqual('one'); + + // Select two + await page.selectOption( + 'text=Pick an option to persist by the State Example extensiononetwothreeCancelOK >> select', + 'two' + ); + + // Check select current value + expect( + await page.$eval( + 'text=Pick an option to persist by the State Example extensiononetwothreeCancelOK >> select', + (s) => s.value + ) + ).toEqual('two'); + + // Click button:has-text("OK") + await Promise.all([ + page.waitForRequest( + (request) => + request.url().search(/\/api\/workspaces\/default/) >= 0 && + request.method() === 'PUT' && + '@jupyterlab-examples/state:state-example' in + request.postDataJSON()?.data + ), + page.click('button:has-text("OK")'), + ]); + + // Reload page + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + expect( + await page.$eval( + 'text=Pick an option to persist by the State Example extensiononetwothreeCancelOK >> select', + (s) => s.value + ) + ).toEqual('two'); +}); diff --git a/toolbar-button/.eslintignore b/toolbar-button/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/toolbar-button/.eslintignore +++ b/toolbar-button/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/toolbar-button/README.md b/toolbar-button/README.md index e36b3f46..c26b6da4 100644 --- a/toolbar-button/README.md +++ b/toolbar-button/README.md @@ -45,13 +45,17 @@ New widgets can be added to a document widget by implementing the interface [Doc document widget; in this case a notebook panel. ```ts -// src/index.ts#L30-L54 +// src/index.ts#L30-L59 export class ButtonExtension implements DocumentRegistry.IWidgetExtension { /** * Create a new extension for the notebook panel widget. + * + * @param panel Notebook panel + * @param context Notebook context + * @returns Disposable on the added button */ createNew( panel: NotebookPanel, @@ -72,16 +76,17 @@ export class ButtonExtension button.dispose(); }); } +} ``` Finally you need to tell the document registry about your widget extension: ```ts -// src/index.ts#L59-L61 +// src/index.ts#L66-L68 - */ -function activate(app: JupyterFrontEnd) { +function activate(app: JupyterFrontEnd): void { app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension()); +} ``` ## Where to Go Next diff --git a/toolbar-button/package.json b/toolbar-button/package.json index 1a3e21a0..423a6a70 100644 --- a/toolbar-button/package.json +++ b/toolbar-button/package.json @@ -30,6 +30,7 @@ }, "scripts": { "build": "jlpm run build:lib && jlpm run build:labextension:dev", + "build:all": "jlpm run build:lib && jlpm run build:labextension", "build:labextension": "jupyter labextension build .", "build:labextension:dev": "jupyter labextension build --development True .", "build:lib": "tsc", @@ -41,6 +42,7 @@ "eslint": "eslint . --ext .ts,.tsx --fix", "eslint:check": "eslint . --ext .ts,.tsx", "install:extension": "jlpm run build", + "prepare": "jlpm run clean && jlpm run build:prod", "watch": "run-p watch:src watch:labextension", "watch:labextension": "jupyter labextension watch .", "watch:src": "tsc -w" diff --git a/toolbar-button/src/index.ts b/toolbar-button/src/index.ts index 4417fb53..8c66487c 100644 --- a/toolbar-button/src/index.ts +++ b/toolbar-button/src/index.ts @@ -32,6 +32,10 @@ export class ButtonExtension { /** * Create a new extension for the notebook panel widget. + * + * @param panel Notebook panel + * @param context Notebook context + * @returns Disposable on the added button */ createNew( panel: NotebookPanel, @@ -56,8 +60,10 @@ export class ButtonExtension /** * Activate the extension. + * + * @param app Main application object */ -function activate(app: JupyterFrontEnd) { +function activate(app: JupyterFrontEnd): void { app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension()); } diff --git a/toolbar-button/ui-tests/.env b/toolbar-button/ui-tests/.env new file mode 100644 index 00000000..c721e3c0 --- /dev/null +++ b/toolbar-button/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=toolbar-button/jupyterlab_examples_toolbar_button +EXT_NAME=toolbar-button \ No newline at end of file diff --git a/toolbar-button/ui-tests/README.md b/toolbar-button/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/toolbar-button/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/toolbar-button/ui-tests/package.json b/toolbar-button/ui-tests/package.json new file mode 100644 index 00000000..f29834ec --- /dev/null +++ b/toolbar-button/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/toolbar-button-tests", + "version": "0.1.0", + "description": "Integration test for toolbar-button example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/toolbar-button/ui-tests/playwright.config.ts b/toolbar-button/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/toolbar-button/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/toolbar-button/ui-tests/tests/toolbar-button.spec.ts b/toolbar-button/ui-tests/tests/toolbar-button.spec.ts new file mode 100644 index 00000000..142f5f8f --- /dev/null +++ b/toolbar-button/ui-tests/tests/toolbar-button.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should clear all outputs when clicked', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Click text=File + await page.click('text=File'); + // Click ul[role="menu"] >> text=New + await page.click('ul[role="menu"] >> text=New'); + // Click #jp-mainmenu-file-new >> text=Notebook + await page.click('#jp-mainmenu-file-new >> text=Notebook'); + // Click button:has-text("Select") + await page.click('button:has-text("Select")'); + + await page.waitForSelector('text=Idle'); + + // Fill textarea + await page.fill('textarea', 'print("Hello, JupyterLab")'); + // Press Enter with modifiers + await page.press('textarea', 'Shift+Enter'); + + // Fill text=[ ]: ​ >> textarea + const secondCell = await page.waitForSelector('text=[ ]: ​ >> textarea'); + await secondCell.fill('print("Welcome to JupyterLab")'); + // Press Enter with modifiers + await secondCell.press('Shift+Enter'); + + // Click .lm-Widget.p-Widget.jp-RenderedText + const OUTPUT = + '.lm-Widget.p-Widget.jp-RenderedText >> text=Hello, JupyterLab'; + expect(await page.waitForSelector(OUTPUT)).toBeTruthy(); + + // Click :nth-match(:text("Hello, JupyterLab"), 2) + // await page.click(':nth-match(:text("Hello, JupyterLab"), 2)'); + + // Click button:has-text("Clear All Outputs") + await page.click('button:has-text("Clear All Outputs")'); + + let failed = true; + try { + await page.waitForSelector(OUTPUT, { timeout: 200 }); + } catch (e) { + failed = false; + expect(e).toBeTruthy(); + } + expect(failed).toBe(false); +}); diff --git a/widgets/.eslintignore b/widgets/.eslintignore index 8d5c8605..8bd31f70 100644 --- a/widgets/.eslintignore +++ b/widgets/.eslintignore @@ -2,3 +2,4 @@ node_modules dist coverage **/*.d.ts +ui-tests diff --git a/widgets/README.md b/widgets/README.md index cc3d7b4f..1a1fdaf1 100644 --- a/widgets/README.md +++ b/widgets/README.md @@ -77,15 +77,18 @@ class ExampleWidget extends Widget { ``` You can associate style properties to the custom CSS class in the file -`style/index.css`: +`style/base.css`: - + + ```css .jp-example-view { background-color: AliceBlue; } + ``` + ## Where to Go Next diff --git a/widgets/style/base.css b/widgets/style/base.css index e69de29b..3166234c 100644 --- a/widgets/style/base.css +++ b/widgets/style/base.css @@ -0,0 +1,3 @@ +.jp-example-view { + background-color: AliceBlue; +} diff --git a/widgets/style/index.css b/widgets/style/index.css index 3166234c..8a7ea29e 100644 --- a/widgets/style/index.css +++ b/widgets/style/index.css @@ -1,3 +1 @@ -.jp-example-view { - background-color: AliceBlue; -} +@import url('base.css'); diff --git a/widgets/ui-tests/.env b/widgets/ui-tests/.env new file mode 100644 index 00000000..c60bd571 --- /dev/null +++ b/widgets/ui-tests/.env @@ -0,0 +1,2 @@ +EXT_FOLDER=widgets/jupyterlab_examples_widgets +EXT_NAME=widgets \ No newline at end of file diff --git a/widgets/ui-tests/README.md b/widgets/ui-tests/README.md new file mode 100644 index 00000000..78aa707b --- /dev/null +++ b/widgets/ui-tests/README.md @@ -0,0 +1,114 @@ +# Test + +The test will produce a video to help debugging and check what happened. + +To execute integration tests, you can two options: + +- use docker-compose (cons: needs to know and use docker) - this is a more reliable solution. +- run tests locally (cons: will interact with your JupyterLab user settings) + +## Test on docker + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Execute the docker stack (`extension-path` needs to be set accordingly): + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env build +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm e2e +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env down +``` + +## Test locally + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Execute in another console the [Playwright](https://playwright.dev/docs/test-intro) tests: + +``` +cd ui-tests +jlpm install +npx playwright test +``` + +# Create tests + +To create tests, the easiest way is to use the code generator tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +npx playwright codegen localhost:8888 +``` + +# Debug tests + +To debug tests, a good way is to use the inspector tool of playwright: + +1. Compile the extension: + +``` +jlpm install +jlpm run build:prod +``` + +2. Start JupyterLab _with the extension installed_ without any token or password: + +**Using docker** + +``` +docker-compose -f ./end-to-end-tests/docker-compose.yml --env-file ./extension-path/ui-tests/.env run --rm -p 8888:8888 lab +``` + +**Using local installation** + +``` +jupyter lab --ServerApp.token= --ServerApp.password= +``` + +3. Launch the code generator tool: + +``` +cd ui-tests +jlpm install +PWDEBUG=1 npx playwright test +``` diff --git a/widgets/ui-tests/package.json b/widgets/ui-tests/package.json new file mode 100644 index 00000000..cd083761 --- /dev/null +++ b/widgets/ui-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "@jupyterlab-examples/widgets-tests", + "version": "0.1.0", + "description": "Integration test for widgets example", + "repository": "https://github.com/jupyterlab/extension-examples", + "author": "Project Jupyter Contributors", + "license": "BSD-3-Clause", + "private": true, + "devDependencies": { + "@playwright/test": "1.12.3", + "typescript": "~4.1.3" + } +} diff --git a/widgets/ui-tests/playwright.config.ts b/widgets/ui-tests/playwright.config.ts new file mode 100644 index 00000000..223d8850 --- /dev/null +++ b/widgets/ui-tests/playwright.config.ts @@ -0,0 +1,18 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + timeout: 60000, + use: { + // Browser options + // headless: false, + // slowMo: 500, + + // Context options + viewport: { width: 1280, height: 720 }, + + // Artifacts + video: 'on', + }, +}; + +export default config; diff --git a/widgets/ui-tests/tests/widgets.spec.ts b/widgets/ui-tests/tests/widgets.spec.ts new file mode 100644 index 00000000..725353ce --- /dev/null +++ b/widgets/ui-tests/tests/widgets.spec.ts @@ -0,0 +1,26 @@ +import { test, expect } from '@playwright/test'; + +const TARGET_URL = process.env.TARGET_URL ?? 'http://localhost:8888'; + +test('should open a widget panel', async ({ page }) => { + await page.goto(`${TARGET_URL}/lab`); + await page.waitForSelector('#jupyterlab-splash', { state: 'detached' }); + await page.waitForSelector('div[role="main"] >> text=Launcher'); + + // Close filebrowser + await page.click('text=View'); + await Promise.all([ + page.waitForSelector('#filebrowser', { state: 'hidden' }), + page.click('ul[role="menu"] >> text=Show Left Sidebar'), + ]); + + // Click text=Widget Example + await page.click('text=Widget Example'); + + // Click ul[role="menu"] >> text=Open a Tab Widget + await page.click('ul[role="menu"] >> text=Open a Tab Widget'); + + await page.click('div[role="main"] >> text=Widget Example View'); + + expect(await page.screenshot()).toMatchSnapshot('widgets-example.png'); +}); diff --git a/widgets/ui-tests/tests/widgets.spec.ts-snapshots/widgets-example-chromium-linux.png b/widgets/ui-tests/tests/widgets.spec.ts-snapshots/widgets-example-chromium-linux.png new file mode 100644 index 00000000..a5b0232f Binary files /dev/null and b/widgets/ui-tests/tests/widgets.spec.ts-snapshots/widgets-example-chromium-linux.png differ