Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some playwright ui tests #33

Merged
merged 11 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,17 @@ jobs:
- name: Install the extension
run: |
set -eux
python -m pip install --pre jupyterlite-core
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there could be a pin on jupyterlite-core here, as new versions may bring some visual changes that would make the UI tests fail?

For example jupyterlite-core==0.5.0 will be based on JupyterLab 4.3 and Notebook 7.3, which may have some CSS changes compared to jupyterlite-core==0.4.x.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll do that.

python -m pip install "jupyterlab>=4.0.0,<5" jupyterlite_terminal*.whl

- name: Install dependencies
working-directory: ui-tests
env:
YARN_ENABLE_IMMUTABLE_INSTALLS: 0
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: jlpm install
run: |
jlpm install
jlpm build

- name: Set up browser cache
uses: actions/cache@v4
Expand All @@ -129,7 +132,7 @@ jobs:
- name: Execute integration tests
working-directory: ui-tests
run: |
jlpm playwright test
jlpm test

- name: Upload Playwright Test report
if: always()
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const terminalsPlugin: JupyterLiteServerPlugin<ITerminals> = {
provides: ITerminals,
activate: async (app: JupyterLiteServer) => {
console.log(
'JupyterLab extension @jupyterlite/terminal:plugin is activated!'
'JupyterLite extension @jupyterlite/terminal:plugin is activated!'
);

const { serviceManager } = app;
Expand All @@ -46,7 +46,7 @@ const terminalsRoutesPlugin: JupyterLiteServerPlugin<void> = {
requires: [ITerminals],
activate: (app: JupyterLiteServer, terminals: ITerminals) => {
console.log(
'JupyterLab extension @jupyterlite/terminal:routes-plugin is activated!',
'JupyterLite extension @jupyterlite/terminal:routes-plugin is activated!',
terminals
);

Expand Down
13 changes: 7 additions & 6 deletions ui-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ and [Galata](https://github.com/jupyterlab/jupyterlab/tree/main/galata) helper.

The Playwright configuration is defined in [playwright.config.js](./playwright.config.js).

The JupyterLab server configuration to use for the integration test is defined
in [jupyter_server_test_config.py](./jupyter_server_test_config.py).

The default configuration will produce video for failing tests and an HTML report.

> There is a new experimental UI mode that you may fall in love with; see [that video](https://www.youtube.com/watch?v=jF0yA-JLQW0).
Expand All @@ -34,6 +31,7 @@ jlpm build:prod
```sh
cd ./ui-tests
jlpm install
jlpm build
jlpm playwright install
cd ..
```
Expand All @@ -42,7 +40,7 @@ cd ..

```sh
cd ./ui-tests
jlpm playwright test
jlpm test
```

Test results will be shown in the terminal. In case of any test failures, the test report
Expand Down Expand Up @@ -71,6 +69,7 @@ jlpm build:prod
```sh
cd ./ui-tests
jlpm install
jlpm build
jlpm playwright install
cd ..
```
Expand All @@ -79,7 +78,7 @@ cd ..

```sh
cd ./ui-tests
jlpm playwright test -u
jlpm test -u
```

> Some discrepancy may occurs between the snapshots generated on your computer and
Expand Down Expand Up @@ -107,6 +106,7 @@ jlpm build:prod
```sh
cd ./ui-tests
jlpm install
jlpm build
jlpm playwright install
cd ..
```
Expand Down Expand Up @@ -145,6 +145,7 @@ jlpm build:prod
```sh
cd ./ui-tests
jlpm install
jlpm build
jlpm playwright install
cd ..
```
Expand All @@ -153,7 +154,7 @@ cd ..

```sh
cd ./ui-tests
jlpm playwright test --debug
jlpm test --debug
```

## Upgrade Playwright and the browsers
Expand Down
17 changes: 17 additions & 0 deletions ui-tests/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Build jupyterlite deployment containing terminal extension for playwright tests.

from pathlib import Path
from subprocess import run

import jupyterlab

extra_labextensions_path = str(Path(jupyterlab.__file__).parent / "galata")
cmd = [
"jupyter",
"lite",
"build",
"--contents",
"../demo/contents",
f"--FederatedExtensionAddon.extra_labextensions_path={extra_labextensions_path}",
]
run(cmd, check=True)
8 changes: 8 additions & 0 deletions ui-tests/jupyter-lite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"jupyter-lite-schema-version": 0,
"jupyter-config-data": {
"appName": "JupyterLite terminal UI Tests",
"exposeAppInBrowser": true,
"terminalsAvailable": true
}
}
5 changes: 5 additions & 0 deletions ui-tests/jupyter_lite_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"LiteBuildConfig": {
"output_dir": "dist"
}
}
12 changes: 0 additions & 12 deletions ui-tests/jupyter_server_test_config.py

This file was deleted.

8 changes: 6 additions & 2 deletions ui-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
"description": "JupyterLab jupyterlite-terminal Integration Tests",
"private": true,
"scripts": {
"start": "jupyter lab --config jupyter_server_test_config.py",
"build": "jlpm clean && python build.py",
"clean": "rimraf dist",
"clean:all": "jlpm clean && rimraf cockle_wasm_env .cockle_temp",
"start": "npx static-handler -p 8000 --cors --coop --coep --corp ./dist",
"test": "jlpm playwright test",
"test:update": "jlpm playwright test --update-snapshots"
},
"devDependencies": {
"@jupyterlab/galata": "^5.0.5",
"@playwright/test": "^1.37.0"
"@playwright/test": "^1.37.0",
"rimraf": "^6.0.1"
}
}
9 changes: 7 additions & 2 deletions ui-tests/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ const baseConfig = require('@jupyterlab/galata/lib/playwright-config');

module.exports = {
...baseConfig,
use: {
acceptDownloads: true,
autoGoto: false,
baseURL: 'http://localhost:8000'
},
webServer: {
command: 'jlpm start',
url: 'http://localhost:8888/lab',
port: 8000,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI
reuseExistingServer: true
}
};
127 changes: 109 additions & 18 deletions ui-tests/tests/jupyterlite_terminal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,115 @@
import { expect, test } from '@jupyterlab/galata';

/**
* Don't load JupyterLab webpage before running the tests.
* This is required to ensure we capture all log messages.
*/
test.use({ autoGoto: false });

// TODO: re-enable when testing with JupyterLite
test.skip('should emit an activation console message', async ({ page }) => {
const logs: string[] = [];

page.on('console', message => {
logs.push(message.text());
const TERMINAL_SELECTOR = '.jp-Terminal';

async function inputLine(page, text: string) {
for (const char of text) {
await page.keyboard.type(char);
await page.waitForTimeout(10);
}
await page.keyboard.press('Enter');
}

test.describe('Terminal extension', () => {
test('should emit activation console messages', async ({ page }) => {
const logs: string[] = [];
page.on('console', message => {
logs.push(message.text());
});

await page.goto();

expect(
logs.filter(s =>
s.match(/^JupyterLite extension @jupyterlite\/terminal:.*is activated!/)
)
).toHaveLength(2);
});
});

test.describe('Terminal', () => {
test('should open via File menu', async ({ page }) => {
await page.goto();
await page.menu.clickMenuItem('File>New>Terminal');
await page.locator(TERMINAL_SELECTOR).waitFor();
});

test('should appear in sidebar', async ({ page }) => {
await page.goto();
await page.menu.clickMenuItem('File>New>Terminal');
await page.locator(TERMINAL_SELECTOR).waitFor();
await page.sidebar.openTab('jp-running-sessions');
await expect(page.locator('text=terminals/1')).toBeVisible();
});

test('should open via launcher', async ({ page }) => {
await page.goto();
await page
.locator('.jp-LauncherCard-label >> p:has-text("Terminal")')
.click();
await page.locator(TERMINAL_SELECTOR).waitFor();
});

test('should create a new file', async ({ page }) => {
await page.goto();
await page.menu.clickMenuItem('File>New>Terminal');
await page.locator(TERMINAL_SELECTOR).waitFor();
await page.locator('div.xterm-screen').click(); // sets focus for keyboard input

await inputLine(page, 'echo Hello > out.txt');
await page.getByTitle('Name: out.txt').waitFor();
});
});

await page.goto();
test.describe('Images', () => {
test('initial', async ({ page }) => {
await page.goto();
await page.menu.clickMenuItem('File>New>Terminal');
await page.locator(TERMINAL_SELECTOR).waitFor();
await page.locator('div.xterm-screen').click(); // sets focus for keyboard input

expect(
logs.filter(
s => s === 'JupyterLab extension @jupyterlite/terminal is activated!'
)
).toHaveLength(1);
// Hide modification times.
const modified = page.locator('span.jp-DirListing-itemModified');
await modified.evaluateAll(els => els.map(el => (el.innerHTML = '')));

const imageName = 'initial.png';
expect(await page.screenshot()).toMatchSnapshot(imageName.toLowerCase());
});

test('various commands', async ({ page }) => {
await page.goto();
await page.menu.clickMenuItem('File>New>Terminal');
await page.locator(TERMINAL_SELECTOR).waitFor();
await page.locator('div.xterm-screen').click(); // sets focus for keyboard input

const wait = 100; // milliseconds

await inputLine(page, 'ls'); // avoid timestamps
await page.waitForTimeout(wait);

await inputLine(page, 'cp months.txt other.txt');
await page.waitForTimeout(wait);

await inputLine(page, 'ls'); // avoid timestamps
await page.waitForTimeout(wait);

await inputLine(page, 'una\t'); // tab complete command name
await page.waitForTimeout(wait);

await inputLine(page, 'grep ember mon\t'); // tab complete filename
await page.waitForTimeout(wait);

await page.keyboard.press('Tab'); // list all commands
await page.waitForTimeout(wait);

await inputLine(page, 'abc'); // no such command
await page.waitForTimeout(wait);

// Hide modification times.
const modified = page.locator('span.jp-DirListing-itemModified');
await modified.evaluateAll(els => els.map(el => (el.innerHTML = '')));

const imageName = 'various-commands.png';
expect(await page.screenshot()).toMatchSnapshot(imageName.toLowerCase());
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading