From 335e501dcec06dfffe465f586dc5daa3249dec3c Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Wed, 27 Jan 2021 23:52:28 +0100 Subject: [PATCH 01/21] Setup playwright for end to end testing --- app/babel.config.js | 1 + app/jest.config.js | 22 +++++++ app/package.json | 11 ++++ app/test/notebook.spec.ts | 59 ++++++++++++++++++ app/tsconfig.test.json | 4 ++ package.json | 1 - yarn.lock | 128 +++++++++++++++++++++++++++++++++++--- 7 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 app/babel.config.js create mode 100644 app/jest.config.js create mode 100644 app/test/notebook.spec.ts create mode 100644 app/tsconfig.test.json diff --git a/app/babel.config.js b/app/babel.config.js new file mode 100644 index 000000000..8b5c76420 --- /dev/null +++ b/app/babel.config.js @@ -0,0 +1 @@ +module.exports = require('@jupyterlab/testutils/lib/babel.config'); diff --git a/app/jest.config.js b/app/jest.config.js new file mode 100644 index 000000000..edf5d40c4 --- /dev/null +++ b/app/jest.config.js @@ -0,0 +1,22 @@ +const func = require('@jupyterlab/testutils/lib/jest-config'); +const upstream = func(__dirname); + +let local = { + preset: 'ts-jest/presets/js-with-babel', + transformIgnorePatterns: ['/node_modules/(?!(@jupyterlab/.*)/)'], + globals: { + 'ts-jest': { + tsconfig: './tsconfig.test.json' + } + }, + transform: { + '\\.(ts|tsx)?$': 'ts-jest', + '\\.svg$': 'jest-raw-loader' + } +}; + +Object.keys(local).forEach(option => { + upstream[option] = local[option]; +}); + +module.exports = upstream; diff --git a/app/package.json b/app/package.json index a85002916..290893899 100644 --- a/app/package.json +++ b/app/package.json @@ -5,8 +5,13 @@ "scripts": { "build": "webpack", "build:prod": "webpack --mode=production", + "build:test": "tsc --build tsconfig.test.json", "clean": "rimraf build", "prepublishOnly": "yarn run build", + "test": "jest", + "test:cov": "jest --collect-coverage", + "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", + "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", "watch": "webpack --watch" }, "dependencies": { @@ -36,14 +41,20 @@ "@jupyterlab/tooltip-extension": "^3.0.0" }, "devDependencies": { + "@babel/core": "^7.11.6", + "@babel/preset-env": "^7.12.1", "@jupyterlab/builder": "^3.0.0", "@jupyterlab/buildutils": "^3.0.0", + "@jupyterlab/testutils": "^3.0.0", + "@types/jest": "^26.0.10", "css-loader": "~5.0.1", "file-loader": "~5.0.2", "fs-extra": "^8.1.0", "glob": "~7.1.6", + "jest": "^26.4.2", "mini-css-extract-plugin": "~0.9.0", "npm-run-all": "^4.1.5", + "playwright": "^1.8.0", "raw-loader": "~4.0.0", "rimraf": "~3.0.2", "style-loader": "~1.0.1", diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts new file mode 100644 index 000000000..4c23be3f0 --- /dev/null +++ b/app/test/notebook.spec.ts @@ -0,0 +1,59 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { chromium, Browser } from 'playwright'; + +const JUPYTERLAB_CLASSIC = + 'http://localhost:8889/classic/notebooks/binder/example.ipynb'; + +const TITLE_XPATH = '//*[@id="jp-title"]/h1'; + +const RENAME_SELECTOR = + 'div.lm-Widget.p-Widget.jp-FileDialog.jp-Dialog-body > input'; + +const ACCEPT_SELECTOR = + 'body > div.lm-Widget.p-Widget.jp-Dialog > div > div.lm-Widget.p-Widget.jp-Dialog-footer > button.jp-Dialog-button.jp-mod-accept.jp-mod-styled'; + +describe('Notebook', () => { + let browser: Browser; + + beforeEach(async () => { + // browser = await chromium.launch({ headless: false, slowMo: 1000 }); + browser = await chromium.launch(); + }); + + afterEach(() => { + browser.close(); + }); + + describe('Title', () => { + it('should be rendered', async () => { + const page = await browser.newPage(); + await page.goto(JUPYTERLAB_CLASSIC); + await page.waitForTimeout(2000); + const href = await page.evaluate(() => { + return document.querySelector('#jp-ClassicLogo')?.getAttribute('href'); + }); + expect(href).toContain('/classic/tree'); + }); + }); + + describe('Renaming', () => { + it('should be possible to rename the notebook', async () => { + const page = await browser.newPage(); + await page.goto(JUPYTERLAB_CLASSIC); + await page.waitForSelector(TITLE_XPATH); + + await page.click(TITLE_XPATH); + + const name = await page.$(RENAME_SELECTOR); + const newName = 'test'; + await name?.type(newName, { delay: 100 }); + + await page.click(ACCEPT_SELECTOR); + + const url = page.url(); + expect(url).toContain(newName); + }); + }); +}); diff --git a/app/tsconfig.test.json b/app/tsconfig.test.json new file mode 100644 index 000000000..ddde3383d --- /dev/null +++ b/app/tsconfig.test.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfigbase.test", + "include": ["test/**/*"] +} diff --git a/package.json b/package.json index 750c843a9..e56ae3b30 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "scripts": { "build": "lerna run build", "build:prod": "lerna run build:prod", - "build:test": "lerna run build:test", "clean": "lerna run clean", "eslint": "eslint . --ext .ts,.tsx --fix", "eslint:check": "eslint . --ext .ts,.tsx", diff --git a/yarn.lock b/yarn.lock index 9e7f551ae..eb1ae8299 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3232,6 +3232,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^4.2.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -3518,6 +3525,13 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -3996,6 +4010,11 @@ btoa-lite@^1.0.0: resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -4443,7 +4462,7 @@ commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^6.2.0: +commander@^6.1.0, commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -4785,6 +4804,13 @@ debug@3.1.0: dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4799,13 +4825,6 @@ debug@^3.1.0: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -5562,6 +5581,17 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -5640,6 +5670,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -6357,6 +6394,14 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -7344,6 +7389,11 @@ jest@^26.4.2: import-local "^3.0.2" jest-cli "^26.6.3" +jpeg-js@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -8055,6 +8105,11 @@ mime@^2.3.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74" integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA== +mime@^2.4.6: + version "2.5.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.0.tgz#2b4af934401779806ee98026bb42e8c1ae1876b1" + integrity sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -9022,6 +9077,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -9092,6 +9152,24 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" +playwright@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.8.0.tgz#8eca2250967ee892b9fdfec44e2358455ab0f8e3" + integrity sha512-urMJDLX92KawbkWKrt3chVVBPQsuuNwlS5St7I5YQENXAEItoyUqX7FjiYaoPgXifKqe1+BKC+7pBAq1QUkgSw== + dependencies: + commander "^6.1.0" + debug "^4.1.1" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + ws "^7.3.1" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -9099,6 +9177,11 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + popper.js@^1.14.4, popper.js@^1.16.1: version "1.16.1" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" @@ -9227,7 +9310,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -9269,6 +9352,15 @@ prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +proper-lockfile@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -9286,6 +9378,11 @@ protoduck@^5.0.1: dependencies: genfun "^5.0.0" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -9869,6 +9966,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -11752,6 +11854,14 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 24cfce0a3888aac7b5b18d12e039e6b438520e32 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 00:39:13 +0100 Subject: [PATCH 02/21] Readd build:test script --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e56ae3b30..750c843a9 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "scripts": { "build": "lerna run build", "build:prod": "lerna run build:prod", + "build:test": "lerna run build:test", "clean": "lerna run clean", "eslint": "eslint . --ext .ts,.tsx --fix", "eslint:check": "eslint . --ext .ts,.tsx", From d9bff3966f2edfecd7f2bf4a039e4d5683cb34e3 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 10:12:50 +0100 Subject: [PATCH 03/21] Add script to start jupyter classic --- app/test/jupyter_server_config.py | 5 +++++ app/test/notebook.spec.ts | 5 ++--- app/test/tree.spec.ts | 30 ++++++++++++++++++++++++++++++ package.json | 2 ++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 app/test/jupyter_server_config.py create mode 100644 app/test/tree.spec.ts diff --git a/app/test/jupyter_server_config.py b/app/test/jupyter_server_config.py new file mode 100644 index 000000000..67f7a315d --- /dev/null +++ b/app/test/jupyter_server_config.py @@ -0,0 +1,5 @@ +c.ServerApp.port = 8889 +c.ServerApp.token = "" +c.ServerApp.password = "" +c.ServerApp.disable_check_xsrf = True +c.ServerApp.open_browser = False diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index 4c23be3f0..4627631de 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -18,8 +18,7 @@ describe('Notebook', () => { let browser: Browser; beforeEach(async () => { - // browser = await chromium.launch({ headless: false, slowMo: 1000 }); - browser = await chromium.launch(); + browser = await chromium.launch({ headless: false, slowMo: 100 }); }); afterEach(() => { @@ -48,7 +47,7 @@ describe('Notebook', () => { const name = await page.$(RENAME_SELECTOR); const newName = 'test'; - await name?.type(newName, { delay: 100 }); + await name?.type(newName); await page.click(ACCEPT_SELECTOR); diff --git a/app/test/tree.spec.ts b/app/test/tree.spec.ts new file mode 100644 index 000000000..57bcd5a8a --- /dev/null +++ b/app/test/tree.spec.ts @@ -0,0 +1,30 @@ +import { chromium, Browser } from 'playwright'; + +const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/tree'; + +const NEW_NOTEBOOK = + '#filebrowser > div.lm-Widget.p-Widget.jp-Toolbar.jp-scrollbar-tiny.jp-FileBrowser-toolbar > div:nth-child(1) > button'; + +describe('Tree', () => { + let browser: Browser; + + beforeEach(async () => { + browser = await chromium.launch({ headless: false, slowMo: 100 }); + }); + + afterEach(() => { + browser.close(); + }); + + describe('File Browser', () => { + it('should be rendered', async () => { + const page = await browser.newPage(); + await page.goto(JUPYTERLAB_CLASSIC); + await page.waitForSelector(NEW_NOTEBOOK); + + const button = await page.$(NEW_NOTEBOOK); + await page.screenshot({ path: 'screenshot.png', fullPage: true }); + expect(button).toBeDefined(); + }); + }); +}); diff --git a/package.json b/package.json index 750c843a9..f96807809 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "release:bump": "node ./buildutils/lib/release-bump.js", "release:npm": "node ./buildutils/lib/release-npm.js", "release:patch": "node ./buildutils/lib/release-patch.js", + "start": "jupyter classic --config ./app/test/jupyter_server_config.py", "test": "lerna run test", + "test:ci": "run-p start test", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": { From b728f8c28be65b986c192e73017552f6e6b4cc9b Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 10:13:55 +0100 Subject: [PATCH 04/21] Add playwright github action --- .github/workflows/build.yml | 3 ++- .github/workflows/release.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ac753d3d..83979fdb5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,10 +55,11 @@ jobs: jlpm jlpm run eslint:check jlpm run prettier:check + - uses: microsoft/playwright-github-action@v1 - name: Test run: | jlpm run build:test - jlpm run test + jlpm run test:ci build: needs: [integrity] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38e8af42a..7a5d80aa3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,10 +55,11 @@ jobs: jlpm jlpm run eslint:check jlpm run prettier:check + - uses: microsoft/playwright-github-action@v1 - name: Test run: | jlpm run build:test - jlpm run test + jlpm run test:ci build: needs: [integrity] From 6c7465b09f70fcfecf38df3ec636e76472594e8b Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 10:26:01 +0100 Subject: [PATCH 05/21] Run headless on CI --- app/test/notebook.spec.ts | 3 ++- app/test/tree.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index 4627631de..c6ede8845 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -18,7 +18,8 @@ describe('Notebook', () => { let browser: Browser; beforeEach(async () => { - browser = await chromium.launch({ headless: false, slowMo: 100 }); + // browser = await chromium.launch({ headless: false, slowMo: 100 }); + browser = await chromium.launch(); }); afterEach(() => { diff --git a/app/test/tree.spec.ts b/app/test/tree.spec.ts index 57bcd5a8a..0f62af585 100644 --- a/app/test/tree.spec.ts +++ b/app/test/tree.spec.ts @@ -9,7 +9,8 @@ describe('Tree', () => { let browser: Browser; beforeEach(async () => { - browser = await chromium.launch({ headless: false, slowMo: 100 }); + // browser = await chromium.launch({ headless: false, slowMo: 100 }); + browser = await chromium.launch(); }); afterEach(() => { From ba396897ed99f7e8a9044c00172e74fb7048fa44 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 14:02:19 +0100 Subject: [PATCH 06/21] Simplify selectors --- app/test/notebook.spec.ts | 30 ++++++++++++++---------------- package.json | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index c6ede8845..0387872dd 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -6,20 +6,12 @@ import { chromium, Browser } from 'playwright'; const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/notebooks/binder/example.ipynb'; -const TITLE_XPATH = '//*[@id="jp-title"]/h1'; - -const RENAME_SELECTOR = - 'div.lm-Widget.p-Widget.jp-FileDialog.jp-Dialog-body > input'; - -const ACCEPT_SELECTOR = - 'body > div.lm-Widget.p-Widget.jp-Dialog > div > div.lm-Widget.p-Widget.jp-Dialog-footer > button.jp-Dialog-button.jp-mod-accept.jp-mod-styled'; - describe('Notebook', () => { let browser: Browser; beforeEach(async () => { - // browser = await chromium.launch({ headless: false, slowMo: 100 }); - browser = await chromium.launch(); + browser = await chromium.launch({ slowMo: 100 }); + // browser = await chromium.launch(); }); afterEach(() => { @@ -42,15 +34,21 @@ describe('Notebook', () => { it('should be possible to rename the notebook', async () => { const page = await browser.newPage(); await page.goto(JUPYTERLAB_CLASSIC); - await page.waitForSelector(TITLE_XPATH); - await page.click(TITLE_XPATH); + // Click text="Untitled.ipynb" + await page.click('text="example.ipynb"'); - const name = await page.$(RENAME_SELECTOR); - const newName = 'test'; - await name?.type(newName); + const newName = 'test.ipynb'; + await page.fill( + "//div[normalize-space(.)='File Pathbinder/example.ipynbNew Name']/input", + newName + ); - await page.click(ACCEPT_SELECTOR); + // Click text="Rename" + await Promise.all([ + page.waitForNavigation(/*{ url: 'http://localhost:8889/classic/notebooks/test.ipynb' }*/), + page.click('text="Rename"') + ]); const url = page.url(); expect(url).toContain(newName); diff --git a/package.json b/package.json index f96807809..9975318f2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "release:bump": "node ./buildutils/lib/release-bump.js", "release:npm": "node ./buildutils/lib/release-npm.js", "release:patch": "node ./buildutils/lib/release-patch.js", - "start": "jupyter classic --config ./app/test/jupyter_server_config.py", + "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", "test:ci": "run-p start test", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" From 4c9927341e7946f0a28650c1fa934aeb7081ab3f Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 14:29:34 +0100 Subject: [PATCH 07/21] Start server in background on CI --- app/package.json | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 290893899..62ef99c50 100644 --- a/app/package.json +++ b/app/package.json @@ -9,6 +9,7 @@ "clean": "rimraf build", "prepublishOnly": "yarn run build", "test": "jest", + "test:pwdebug": "PWDEBUG=1 jest", "test:cov": "jest --collect-coverage", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", diff --git a/package.json b/package.json index 9975318f2..a1a1864a1 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "release:patch": "node ./buildutils/lib/release-patch.js", "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", - "test:ci": "run-p start test", + "test:ci": "(jlpm run start&) && jlpm run test", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": { From 2db4d689d284431b2173f12aad258b4557593928 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 16:01:17 +0100 Subject: [PATCH 08/21] Add a smoke test with video recording --- app/test/smoke.spec.ts | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 app/test/smoke.spec.ts diff --git a/app/test/smoke.spec.ts b/app/test/smoke.spec.ts new file mode 100644 index 000000000..1749447d3 --- /dev/null +++ b/app/test/smoke.spec.ts @@ -0,0 +1,83 @@ +import { chromium, Browser, BrowserContext } from 'playwright'; + +describe('Smoke', () => { + let browser: Browser; + let context: BrowserContext; + + beforeAll(async () => { + jest.setTimeout(200000); + browser = await chromium.launch({ slowMo: 1000 }); + context = await browser.newContext({ recordVideo: { dir: 'videos/' } }); + }); + + afterAll(async () => { + await context.close(); + await browser.close(); + }); + + describe('Tour', () => { + it('should open a new new notebook and stop the kernel after', async () => { + const tree = await context.newPage(); + + // Open the tree page + await tree.goto('http://localhost:8889/classic/tree?'); + await tree.click('text="Running"'); + await tree.click('text="Files"'); + + // Create a new notebook + const [notebook] = await Promise.all([ + tree.waitForEvent('popup'), + tree.click('text="New Notebook"') + ]); + + // Choose the kernel + await notebook.click('text="Select"'); + await notebook.click('pre[role="presentation"]'); + + // Enter code in the first cell + await notebook.fill('//textarea', 'import math'); + await notebook.press('//textarea', 'Enter'); + await notebook.press('//textarea', 'Enter'); + await notebook.fill('//textarea', 'math.pi'); + + // Run the cell + await notebook.click( + "//button[normalize-space(@title)='Run the selected cells and advance']" + ); + + // Enter code in the next cell + await notebook.fill( + "//div[normalize-space(.)=' ​']/div[1]/textarea", + 'import this' + ); + + // Run the cell + await notebook.click( + '//button[normalize-space(@title)=\'Run the selected cells and advance\']/span/span/*[local-name()="svg"]' + ); + + // Save the notebook + await notebook.click('//span/*[local-name()="svg"]'); + + // Click on the Jupyter logo to open the tree page + const [tree2] = await Promise.all([ + notebook.waitForEvent('popup'), + notebook.click( + '//*[local-name()="svg" and normalize-space(.)=\'Jupyter\']' + ) + ]); + + // Shut down the kernels + await tree2.click('text="Running"'); + await tree2.click('text="Shut Down All"'); + await tree2.click("//div[normalize-space(.)='Shut Down All']"); + + // Close the pages + await tree2.close(); + await notebook.close(); + await tree.close(); + + expect(true).toBe(true); + }); + }); +}); From 486c670b52c2336fd48b66aad23a0cb30310099f Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 16:05:53 +0100 Subject: [PATCH 09/21] Upload artifacts --- .github/workflows/build.yml | 5 +++++ .github/workflows/release.yml | 5 +++++ app/test/smoke.spec.ts | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83979fdb5..fcf272f63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,6 +60,11 @@ jobs: run: | jlpm run build:test jlpm run test:ci + - uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: test-artifacts + path: app/artifacts build: needs: [integrity] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a5d80aa3..7a292bcb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,11 @@ jobs: run: | jlpm run build:test jlpm run test:ci + - uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: test-artifacts + path: app/artifacts build: needs: [integrity] diff --git a/app/test/smoke.spec.ts b/app/test/smoke.spec.ts index 1749447d3..8351b6c1e 100644 --- a/app/test/smoke.spec.ts +++ b/app/test/smoke.spec.ts @@ -7,7 +7,9 @@ describe('Smoke', () => { beforeAll(async () => { jest.setTimeout(200000); browser = await chromium.launch({ slowMo: 1000 }); - context = await browser.newContext({ recordVideo: { dir: 'videos/' } }); + context = await browser.newContext({ + recordVideo: { dir: 'artifacts/videos/' } + }); }); afterAll(async () => { From d68ff10f4d8cf5784bb10d8a7804404ca2412cb7 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 16:44:46 +0100 Subject: [PATCH 10/21] Switch to firefox for testing --- .gitignore | 3 +++ app/test/smoke.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7f5d94ce0..651278741 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,6 @@ junit.xml [uU]ntitled* jupyterlab_classic/static jupyterlab_classic/labextension + +# playwright +app/artifacts/videos diff --git a/app/test/smoke.spec.ts b/app/test/smoke.spec.ts index 8351b6c1e..f9fdfa0d8 100644 --- a/app/test/smoke.spec.ts +++ b/app/test/smoke.spec.ts @@ -1,4 +1,4 @@ -import { chromium, Browser, BrowserContext } from 'playwright'; +import { firefox, Browser, BrowserContext } from 'playwright'; describe('Smoke', () => { let browser: Browser; @@ -6,7 +6,7 @@ describe('Smoke', () => { beforeAll(async () => { jest.setTimeout(200000); - browser = await chromium.launch({ slowMo: 1000 }); + browser = await firefox.launch({ slowMo: 1000 }); context = await browser.newContext({ recordVideo: { dir: 'artifacts/videos/' } }); From f916ee0c708d4452ee2c73d8d7fc337123d98541 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 18:19:38 +0100 Subject: [PATCH 11/21] Cleanup artifacts before running the tests --- app/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/package.json b/app/package.json index 62ef99c50..d9947cabc 100644 --- a/app/package.json +++ b/app/package.json @@ -7,9 +7,10 @@ "build:prod": "webpack --mode=production", "build:test": "tsc --build tsconfig.test.json", "clean": "rimraf build", + "clean:artifacts": "rimraf artifacts", "prepublishOnly": "yarn run build", - "test": "jest", - "test:pwdebug": "PWDEBUG=1 jest", + "test": "jlpm run clean:artifacts && jest", + "test:pwdebug": "jlpm run clean:artifacts && PWDEBUG=1 jlpm run test", "test:cov": "jest --collect-coverage", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", From 677eca81971823ddb53daaa5618e4a8248b4e882 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Thu, 28 Jan 2021 23:52:26 +0100 Subject: [PATCH 12/21] Setup test notebook data --- .gitignore | 1 + app/jest-setup.js | 13 +++++++++++++ app/jest.config.js | 2 ++ app/test/notebook.spec.ts | 5 ++--- app/test/tree.spec.ts | 4 +--- 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 app/jest-setup.js diff --git a/.gitignore b/.gitignore index 651278741..16dd79b44 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,4 @@ jupyterlab_classic/labextension # playwright app/artifacts/videos +app/test/data diff --git a/app/jest-setup.js b/app/jest-setup.js new file mode 100644 index 000000000..80e599f3d --- /dev/null +++ b/app/jest-setup.js @@ -0,0 +1,13 @@ +const fs = require('fs'); +const path = require('path'); +const rimraf = require('rimraf'); + +module.exports = async () => { + const data = path.join(__dirname, './test/data'); + const example = path.join(__dirname, '../binder/example.ipynb'); + const dest = path.join(data, 'example.ipynb'); + + rimraf.sync(data); + fs.mkdirSync(data); + fs.copyFileSync(example, dest); +}; diff --git a/app/jest.config.js b/app/jest.config.js index edf5d40c4..511aaedfe 100644 --- a/app/jest.config.js +++ b/app/jest.config.js @@ -1,7 +1,9 @@ +const path = require('path'); const func = require('@jupyterlab/testutils/lib/jest-config'); const upstream = func(__dirname); let local = { + globalSetup: path.resolve(__dirname, './jest-setup.js'), preset: 'ts-jest/presets/js-with-babel', transformIgnorePatterns: ['/node_modules/(?!(@jupyterlab/.*)/)'], globals: { diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index 0387872dd..051fdda0f 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -4,14 +4,13 @@ import { chromium, Browser } from 'playwright'; const JUPYTERLAB_CLASSIC = - 'http://localhost:8889/classic/notebooks/binder/example.ipynb'; + 'http://localhost:8889/classic/notebooks/app/test/data/example.ipynb'; describe('Notebook', () => { let browser: Browser; beforeEach(async () => { browser = await chromium.launch({ slowMo: 100 }); - // browser = await chromium.launch(); }); afterEach(() => { @@ -40,7 +39,7 @@ describe('Notebook', () => { const newName = 'test.ipynb'; await page.fill( - "//div[normalize-space(.)='File Pathbinder/example.ipynbNew Name']/input", + "//div[normalize-space(.)='File Pathapp/test/data/example.ipynbNew Name']/input", newName ); diff --git a/app/test/tree.spec.ts b/app/test/tree.spec.ts index 0f62af585..cdc1866cb 100644 --- a/app/test/tree.spec.ts +++ b/app/test/tree.spec.ts @@ -9,8 +9,7 @@ describe('Tree', () => { let browser: Browser; beforeEach(async () => { - // browser = await chromium.launch({ headless: false, slowMo: 100 }); - browser = await chromium.launch(); + browser = await chromium.launch({ slowMo: 100 }); }); afterEach(() => { @@ -24,7 +23,6 @@ describe('Tree', () => { await page.waitForSelector(NEW_NOTEBOOK); const button = await page.$(NEW_NOTEBOOK); - await page.screenshot({ path: 'screenshot.png', fullPage: true }); expect(button).toBeDefined(); }); }); From f889cb46213c34a386364a87fa2ef271ec0c55c3 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 00:34:07 +0100 Subject: [PATCH 13/21] Test on firefox and chromium on CI --- .github/workflows/build.yml | 45 +++++++++++++++++++++++++++++------ .github/workflows/release.yml | 8 +------ app/package.json | 4 ++-- app/test/notebook.spec.ts | 7 ++++-- app/test/smoke.spec.ts | 7 ++++-- app/test/tree.spec.ts | 16 ++++++------- app/test/utils.ts | 1 + package.json | 2 +- 8 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 app/test/utils.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fcf272f63..c017365f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,16 +55,10 @@ jobs: jlpm jlpm run eslint:check jlpm run prettier:check - - uses: microsoft/playwright-github-action@v1 - name: Test run: | jlpm run build:test - jlpm run test:ci - - uses: actions/upload-artifact@v2 - if: ${{ always() }} - with: - name: test-artifacts - path: app/artifacts + jlpm run test build: needs: [integrity] @@ -158,3 +152,40 @@ jobs: jupyter server extension list 2>&1 | grep -ie "jupyterlab_classic.*enabled" - jupyter classic --version jupyter classic --help + + end2end: + needs: [build] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + browser: [firefox, chromium] + steps: + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + architecture: 'x64' + - uses: actions/download-artifact@v2 + with: + name: dist ${{ github.run_number }} + path: ./dist + - name: Install the prerequisites + run: | + python -m pip install pip wheel + - name: Install the package + run: | + cd dist + python -m pip install -vv jupyterlab_classic*.whl + - uses: microsoft/playwright-github-action@v1 + - name: Test + run: | + jlpm run build:test + jlpm run test:e2e + env: + BROWSER: ${{ matrix.browser }} + - uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: test-artifacts + path: app/artifacts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a292bcb9..38e8af42a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,16 +55,10 @@ jobs: jlpm jlpm run eslint:check jlpm run prettier:check - - uses: microsoft/playwright-github-action@v1 - name: Test run: | jlpm run build:test - jlpm run test:ci - - uses: actions/upload-artifact@v2 - if: ${{ always() }} - with: - name: test-artifacts - path: app/artifacts + jlpm run test build: needs: [integrity] diff --git a/app/package.json b/app/package.json index d9947cabc..6cfd49c8f 100644 --- a/app/package.json +++ b/app/package.json @@ -9,8 +9,8 @@ "clean": "rimraf build", "clean:artifacts": "rimraf artifacts", "prepublishOnly": "yarn run build", - "test": "jlpm run clean:artifacts && jest", - "test:pwdebug": "jlpm run clean:artifacts && PWDEBUG=1 jlpm run test", + "test:e2e": "jlpm run clean:artifacts && jest", + "test:e2e:pwdebug": "jlpm run clean:artifacts && PWDEBUG=1 jlpm run test:e2e", "test:cov": "jest --collect-coverage", "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch", diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index 051fdda0f..800f8f78a 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -1,7 +1,9 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { chromium, Browser } from 'playwright'; +import { chromium, firefox, Browser } from 'playwright'; + +import { BrowserName } from './utils'; const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/notebooks/app/test/data/example.ipynb'; @@ -10,7 +12,8 @@ describe('Notebook', () => { let browser: Browser; beforeEach(async () => { - browser = await chromium.launch({ slowMo: 100 }); + const browserName = (process.env.BROWSER as BrowserName) || 'chromium'; + browser = await { chromium, firefox }[browserName].launch({ slowMo: 100 }); }); afterEach(() => { diff --git a/app/test/smoke.spec.ts b/app/test/smoke.spec.ts index f9fdfa0d8..9f326feab 100644 --- a/app/test/smoke.spec.ts +++ b/app/test/smoke.spec.ts @@ -1,4 +1,6 @@ -import { firefox, Browser, BrowserContext } from 'playwright'; +import { chromium, firefox, Browser, BrowserContext } from 'playwright'; + +import { BrowserName } from './utils'; describe('Smoke', () => { let browser: Browser; @@ -6,7 +8,8 @@ describe('Smoke', () => { beforeAll(async () => { jest.setTimeout(200000); - browser = await firefox.launch({ slowMo: 1000 }); + const browserName = (process.env.BROWSER as BrowserName) || 'chromium'; + browser = await { chromium, firefox }[browserName].launch({ slowMo: 100 }); context = await browser.newContext({ recordVideo: { dir: 'artifacts/videos/' } }); diff --git a/app/test/tree.spec.ts b/app/test/tree.spec.ts index cdc1866cb..0d07db24a 100644 --- a/app/test/tree.spec.ts +++ b/app/test/tree.spec.ts @@ -1,15 +1,16 @@ -import { chromium, Browser } from 'playwright'; +import { chromium, firefox, Browser } from 'playwright'; -const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/tree'; +import { BrowserName } from './utils'; -const NEW_NOTEBOOK = - '#filebrowser > div.lm-Widget.p-Widget.jp-Toolbar.jp-scrollbar-tiny.jp-FileBrowser-toolbar > div:nth-child(1) > button'; +const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/tree'; describe('Tree', () => { let browser: Browser; beforeEach(async () => { - browser = await chromium.launch({ slowMo: 100 }); + const browserName: BrowserName = + (process.env.BROWSER as BrowserName) || 'chromium'; + browser = await { chromium, firefox }[browserName].launch({ slowMo: 100 }); }); afterEach(() => { @@ -17,12 +18,11 @@ describe('Tree', () => { }); describe('File Browser', () => { - it('should be rendered', async () => { + it('should render a New Notebook button', async () => { const page = await browser.newPage(); await page.goto(JUPYTERLAB_CLASSIC); - await page.waitForSelector(NEW_NOTEBOOK); - const button = await page.$(NEW_NOTEBOOK); + const button = await page.$('text="New Notebook"'); expect(button).toBeDefined(); }); }); diff --git a/app/test/utils.ts b/app/test/utils.ts new file mode 100644 index 000000000..41d8bb460 --- /dev/null +++ b/app/test/utils.ts @@ -0,0 +1 @@ +export type BrowserName = 'chromium' | 'firefox'; diff --git a/package.json b/package.json index a1a1864a1..3a38ab15c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "release:patch": "node ./buildutils/lib/release-patch.js", "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", - "test:ci": "(jlpm run start&) && jlpm run test", + "test:e2e": "(jlpm run start&) && jlpm run test:e2e", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": { From 03f9eba97dc68733ad9e289bb055cd01bf7f687b Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 00:41:55 +0100 Subject: [PATCH 14/21] Checkout repo in end2end workflow --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c017365f4..7e2e50957 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,6 +161,8 @@ jobs: matrix: browser: [firefox, chromium] steps: + - name: Checkout + uses: actions/checkout@v2 - name: Install Python uses: actions/setup-python@v2 with: From 606752d43acd31d82e234e1221200a6a8d3889c1 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 00:53:48 +0100 Subject: [PATCH 15/21] Rename script to test:ci --- .github/workflows/build.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e2e50957..3b2cff425 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -183,7 +183,7 @@ jobs: - name: Test run: | jlpm run build:test - jlpm run test:e2e + jlpm run test:ci env: BROWSER: ${{ matrix.browser }} - uses: actions/upload-artifact@v2 diff --git a/package.json b/package.json index 3a38ab15c..e3c0790f2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "release:patch": "node ./buildutils/lib/release-patch.js", "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", - "test:e2e": "(jlpm run start&) && jlpm run test:e2e", + "test:ci": "(jlpm run start&) && jlpm run test:e2e", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": { From 8b1bcac352ac4c3ca9ccc22ed813c7a7f96cd8c3 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 00:59:53 +0100 Subject: [PATCH 16/21] Install dependencies --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b2cff425..da1f0ce4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -182,6 +182,7 @@ jobs: - uses: microsoft/playwright-github-action@v1 - name: Test run: | + jlpm jlpm run build:test jlpm run test:ci env: From 88c93a387d6456ff6e5bab992cf5f323abfb4868 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 01:08:27 +0100 Subject: [PATCH 17/21] Fix start script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3c0790f2..37260ba16 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "release:patch": "node ./buildutils/lib/release-patch.js", "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", - "test:ci": "(jlpm run start&) && jlpm run test:e2e", + "test:ci": "(jlpm run start&) && lerna run test:e2e", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": { From e1c31975666a6fcf34a0d7f1737c5596aeff47be Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 01:19:50 +0100 Subject: [PATCH 18/21] Upload artifacts per browser --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da1f0ce4c..f19425c9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,5 +190,5 @@ jobs: - uses: actions/upload-artifact@v2 if: ${{ always() }} with: - name: test-artifacts + name: test-artifacts-${{ matrix.browser }} path: app/artifacts From 59728fbbd911875227760488ce4752eadaf34574 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 16:33:47 +0100 Subject: [PATCH 19/21] Set base url --- app/test/notebook.spec.ts | 20 ++++++++++---------- app/test/smoke.spec.ts | 4 ++-- app/test/tree.spec.ts | 6 ++---- app/test/utils.ts | 2 ++ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/test/notebook.spec.ts b/app/test/notebook.spec.ts index 800f8f78a..3d0b133aa 100644 --- a/app/test/notebook.spec.ts +++ b/app/test/notebook.spec.ts @@ -3,10 +3,10 @@ import { chromium, firefox, Browser } from 'playwright'; -import { BrowserName } from './utils'; +import { BrowserName, BASE_URL } from './utils'; -const JUPYTERLAB_CLASSIC = - 'http://localhost:8889/classic/notebooks/app/test/data/example.ipynb'; +const NOTEBOOK_PATH = 'app/test/data/example.ipynb'; +const NOTEBOOK_URL = `${BASE_URL}classic/notebooks/${NOTEBOOK_PATH}`; describe('Notebook', () => { let browser: Browser; @@ -23,7 +23,7 @@ describe('Notebook', () => { describe('Title', () => { it('should be rendered', async () => { const page = await browser.newPage(); - await page.goto(JUPYTERLAB_CLASSIC); + await page.goto(NOTEBOOK_URL); await page.waitForTimeout(2000); const href = await page.evaluate(() => { return document.querySelector('#jp-ClassicLogo')?.getAttribute('href'); @@ -35,23 +35,23 @@ describe('Notebook', () => { describe('Renaming', () => { it('should be possible to rename the notebook', async () => { const page = await browser.newPage(); - await page.goto(JUPYTERLAB_CLASSIC); + await page.goto(NOTEBOOK_URL); - // Click text="Untitled.ipynb" + // Click on the title await page.click('text="example.ipynb"'); + // Rename in the input dialog const newName = 'test.ipynb'; await page.fill( - "//div[normalize-space(.)='File Pathapp/test/data/example.ipynbNew Name']/input", + `//div[normalize-space(.)='File Path${NOTEBOOK_PATH}New Name']/input`, newName ); - - // Click text="Rename" await Promise.all([ - page.waitForNavigation(/*{ url: 'http://localhost:8889/classic/notebooks/test.ipynb' }*/), + page.waitForNavigation(), page.click('text="Rename"') ]); + // Check the URL contains the new name const url = page.url(); expect(url).toContain(newName); }); diff --git a/app/test/smoke.spec.ts b/app/test/smoke.spec.ts index 9f326feab..8fe89dc9a 100644 --- a/app/test/smoke.spec.ts +++ b/app/test/smoke.spec.ts @@ -1,6 +1,6 @@ import { chromium, firefox, Browser, BrowserContext } from 'playwright'; -import { BrowserName } from './utils'; +import { BrowserName, BASE_URL } from './utils'; describe('Smoke', () => { let browser: Browser; @@ -25,7 +25,7 @@ describe('Smoke', () => { const tree = await context.newPage(); // Open the tree page - await tree.goto('http://localhost:8889/classic/tree?'); + await tree.goto(`${BASE_URL}classic/tree`); await tree.click('text="Running"'); await tree.click('text="Files"'); diff --git a/app/test/tree.spec.ts b/app/test/tree.spec.ts index 0d07db24a..36c1f8ab4 100644 --- a/app/test/tree.spec.ts +++ b/app/test/tree.spec.ts @@ -1,8 +1,6 @@ import { chromium, firefox, Browser } from 'playwright'; -import { BrowserName } from './utils'; - -const JUPYTERLAB_CLASSIC = 'http://localhost:8889/classic/tree'; +import { BASE_URL, BrowserName } from './utils'; describe('Tree', () => { let browser: Browser; @@ -20,7 +18,7 @@ describe('Tree', () => { describe('File Browser', () => { it('should render a New Notebook button', async () => { const page = await browser.newPage(); - await page.goto(JUPYTERLAB_CLASSIC); + await page.goto(`${BASE_URL}classic/tree`); const button = await page.$('text="New Notebook"'); expect(button).toBeDefined(); diff --git a/app/test/utils.ts b/app/test/utils.ts index 41d8bb460..95959ee48 100644 --- a/app/test/utils.ts +++ b/app/test/utils.ts @@ -1 +1,3 @@ export type BrowserName = 'chromium' | 'firefox'; + +export const BASE_URL = 'http://localhost:8889/'; From 21c9ea3912253a94ba42697ae0484973e73f5082 Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 16:54:22 +0100 Subject: [PATCH 20/21] Add github run number to uploaded artifacts --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f19425c9c..4efd5c323 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,5 +190,5 @@ jobs: - uses: actions/upload-artifact@v2 if: ${{ always() }} with: - name: test-artifacts-${{ matrix.browser }} + name: test-artifacts-${{ matrix.browser }} ${{ github.run_number }} path: app/artifacts From caa2d6b9121f71fa47c5c67c9b7eb724306e450c Mon Sep 17 00:00:00 2001 From: Jeremy Tuloup Date: Fri, 29 Jan 2021 17:15:02 +0100 Subject: [PATCH 21/21] Add section about tests to CONTRIBUTING.md --- CONTRIBUTING.md | 29 +++++++++++++++++++++++++++++ package.json | 3 ++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 666cdb65b..08f94037d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,3 +56,32 @@ Then start JupyterLab Classic with: ```bash jupyter classic ``` + +## Running Tests + +To run the tests: + +```bash +jlpm run build:test +jlpm run test +``` + +There are also end to end tests to cover higher level user interactions, located in the `app/test` folder. To run these tests: + +```bash +# start a new Jupyter server in a terminal +npm start + +# run the end to end tests +jlpm run test:e2e + +# to run in headful mode +PWDEBUG=1 jlpm run test:e2e + +# to run with firefox as the browser +BROWSER=firefox jlpm run test:e2e +``` + +Running the end to end tests in headful mode will trigger something like the following: + +![end-to-end-smoke](https://user-images.githubusercontent.com/591645/106299215-34a67b00-6255-11eb-854c-756a8790246b.gif) diff --git a/package.json b/package.json index 37260ba16..dc0b92419 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "release:patch": "node ./buildutils/lib/release-patch.js", "start": "jupyter classic --config ./app/test/jupyter_server_config.py --no-browser", "test": "lerna run test", - "test:ci": "(jlpm run start&) && lerna run test:e2e", + "test:e2e": "lerna run test:e2e --stream", + "test:ci": "(jlpm run start&) && jlpm run test:e2e", "update:dependency": "node ./node_modules/@jupyterlab/buildutils/lib/update-dependency.js --lerna" }, "husky": {