diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 3b2a1ed3e..8f681ac55 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -7,7 +7,7 @@ on: jobs: check_release: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: group: [check_release, link_check] diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 20f4e96b5..4ff23bd01 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,11 +6,11 @@ on: branches: '*' jobs: build: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu] + os: [ubuntu-22.04] python-version: [ '3.7' ] steps: - name: Checkout diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index c58d21adb..b67bf264e 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -8,7 +8,7 @@ on: jobs: downstream: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 15 steps: @@ -18,15 +18,9 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Test jupyterlab_server - uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 - with: - package_name: jupyterlab_server - - name: Test jupyterlab uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: jupyterlab package_spec: "\".[test]\"" test_command: "python -m jupyterlab.browser_check --no-browser-test" - diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml index 354a0468d..5ffb484ea 100644 --- a/.github/workflows/enforce-label.yml +++ b/.github/workflows/enforce-label.yml @@ -5,7 +5,7 @@ on: types: [labeled, unlabeled, opened, edited, synchronize] jobs: enforce-label: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: enforce-triage-label uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index b215521dd..dde806f1c 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -8,11 +8,11 @@ on: jobs: build: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu, macos] + os: [ubuntu-22.04, macos-12] group: [notebook, base, services] exclude: - group: services @@ -20,7 +20,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: 3.8 - name: Set up Node diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3320de8bb..82a1762e9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -7,21 +7,20 @@ on: branches: '*' jobs: build: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu, macos] + os: [ubuntu-22.04, macos-12] python-version: [ '3.7', '3.8', '3.9', '3.10'] steps: - name: Checkout uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - architecture: 'x64' - name: Set up Node uses: actions/setup-node@v1 diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index eac1ce0b9..7311883f7 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -8,15 +8,12 @@ on: jobs: build: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu, macos, windows] - python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.8"] - exclude: - - os: windows - python-version: pypy-3.8 + os: [ubuntu-22.04, macos-12, windows-2022] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Checkout uses: actions/checkout@v2 @@ -33,13 +30,8 @@ jobs: run: | jupyter nbclassic -h - name: Test with pytest and coverage - if: ${{ matrix.python-version != 'pypy-3.8' }} run: | python -m pytest -vv --cov=nbclassic --cov-report term-missing:skip-covered || python -m pytest -vv --cov=nbclassic --cov-report term-missing:skip-covered - - name: Run the tests on pypy - if: ${{ matrix.python-version == 'pypy-3.8' }} - run: | - python -m pytest -vv || python -m pytest -vv -lf - name: Test Running Server if: startsWith(runner.os, 'Linux') run: | diff --git a/nbclassic/static/notebook/js/cell.js b/nbclassic/static/notebook/js/cell.js index 4fd4ec442..69d6d32d1 100644 --- a/nbclassic/static/notebook/js/cell.js +++ b/nbclassic/static/notebook/js/cell.js @@ -104,6 +104,14 @@ define([ this.cell_id = utils.uuid(); + // Similar to nbformat, which uses `uuid.uuid4().hex[:8]` + // as recommended by JEP 62. + // We do not check the nbformat version here, as the notebook will report + // the incorrect (4, 1) nbformat version while calling + // insert_cell_below(...) in load_notebook_success (in notebook.js). + // To not break with nbformat < 4.5, we do not include this field in toJSON. + this.id = utils.uuid().slice(0, 8); + // For JS VM engines optimization, attributes should be all set (even // to null) in the constructor, and if possible, if different subclass // have new attributes with same name, they should be created in the @@ -494,7 +502,10 @@ define([ var data = {}; // deepcopy the metadata so copied cells don't share the same object data.metadata = JSON.parse(JSON.stringify(this.metadata)); - if (this.id !== undefined) { + // id's are only introduced in 4.5 and should only be added to 'raw', 'code' and 'markdown' cells + var nbformatSupportsIds = (Jupyter.notebook.nbformat == 4 && Jupyter.notebook.nbformat_minor >= 5) || (Jupyter.notebook.nbformat > 4); + var cellTypeCanIncludeId = this.cell_type == 'raw' || this.cell_type == 'code' || this.cell_type == 'markdown'; + if (nbformatSupportsIds && cellTypeCanIncludeId) { data.id = this.id; } if (data.metadata.deletable) { diff --git a/nbclassic/tests/end_to_end/test_save_readonly_as.py b/nbclassic/tests/end_to_end/test_save_readonly_as.py index e6f671625..2da7384ae 100644 --- a/nbclassic/tests/end_to_end/test_save_readonly_as.py +++ b/nbclassic/tests/end_to_end/test_save_readonly_as.py @@ -1,8 +1,11 @@ """Test readonly notebook saved and renamed""" +import sys import traceback +import pytest + from .utils import EDITOR_PAGE, EndToEndTimeout @@ -20,7 +23,10 @@ def set_notebook_name(nb, name): JS = f'() => Jupyter.notebook.rename("{name}")' nb.evaluate(JS, page=EDITOR_PAGE) - +# on Python 3.7 we get an old playwright which hangs on body.press("Escape") +@pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher due to playwright") +# and we also see this fail on osx randomly +@pytest.mark.skipif(sys.platform == 'darwin', reason="fails randomly on osx") def test_save_readonly_as(notebook_frontend): print('[Test] [test_save_readonly_as]') notebook_frontend.wait_for_kernel_ready() @@ -114,15 +120,7 @@ def attempt_form_fill_and_save(): save_element.focus() save_element.click() - # Application lag may cause the save dialog to linger, - # if it's visible wait for it to disappear before proceeding - if save_element.is_visible(): - print('[Test] Save element still visible after save, wait for hidden') - try: - save_element.expect_not_to_be_visible(timeout=120) - except EndToEndTimeout as err: - traceback.print_exc() - print('[Test] Save button failed to hide...') + save_element.wait_for('detached') # Check if the save operation succeeded (by checking notebook name change) notebook_frontend.wait_for_condition( diff --git a/nbclassic/tests/end_to_end/test_trust.py b/nbclassic/tests/end_to_end/test_trust.py new file mode 100644 index 000000000..f9860326d --- /dev/null +++ b/nbclassic/tests/end_to_end/test_trust.py @@ -0,0 +1,30 @@ +from .utils import EDITOR_PAGE + +trust_test_code = """ +import IPython.display +IPython.display.Javascript("window.javascriptExecuted = true") +""" + + +def test_trust(notebook_frontend): + def save_notebook(): + notebook_frontend.evaluate("() => Jupyter.notebook.save_notebook()", page=EDITOR_PAGE) + + # Add a cell that executes javascript + notebook_frontend.add_cell(index=0, content=trust_test_code) + notebook_frontend.execute_cell(0) + notebook_frontend.wait_for_cell_output(0) + # Make sure the JavaScript executed + assert notebook_frontend.evaluate("() => window.javascriptExecuted", page=EDITOR_PAGE) == True + # Check that we see the 'Trusted' text on the page + trusted = notebook_frontend.locate("#notification_trusted", page=EDITOR_PAGE) + assert trusted.get_inner_text() == "Trusted" + save_notebook() + + # refresh the page, should be trusted + notebook_frontend.reload(EDITOR_PAGE) + notebook_frontend.wait_for_kernel_ready() + trusted = notebook_frontend.locate("#notification_trusted", page=EDITOR_PAGE) + assert trusted.get_inner_text() == "Trusted" + notebook_frontend.wait_for_cell_output(0) + assert notebook_frontend.evaluate("() => window.javascriptExecuted", page=EDITOR_PAGE) == True diff --git a/nbclassic/tests/end_to_end/utils.py b/nbclassic/tests/end_to_end/utils.py index 9ad5d02bd..b380de8d6 100644 --- a/nbclassic/tests/end_to_end/utils.py +++ b/nbclassic/tests/end_to_end/utils.py @@ -881,7 +881,8 @@ def get_page_url(self, page): else: raise Exception('Error, provide a valid page to evaluate from!') - return specified_page.url + # specified_page.url seems to give stale values, so we use evaluate instead + return specified_page.evaluate("() => { return window.location.href }") def go_back(self, page): if page == TREE_PAGE: diff --git a/nbclassic/tests/util.js b/nbclassic/tests/util.js index 016c13e94..695daa048 100644 --- a/nbclassic/tests/util.js +++ b/nbclassic/tests/util.js @@ -29,11 +29,13 @@ casper.open_new_notebook = function () { this.waitForPopup(''); - this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');}); + this.waitFor(this.popup_has_url); this.then(function () { + console.log('Opening a new notebook under URL: ' + this.popups[0].url); this.open(this.popups[0].url); }); this.waitFor(this.page_loaded); + this.waitForSelector('.CodeMirror-code'); // Hook the log and error methods of the console, forcing them to // serialize their arguments before printing. This allows the @@ -88,6 +90,12 @@ casper.page_loaded = function() { }); }; +casper.popup_has_url = function() { + // Return whether there is a popup with non blank URL. + var url = this.popups[0].url; + return typeof url !== 'undefined' && url != 'about:blank'; +}; + casper.kernel_running = function() { // Return whether or not the kernel is running. return this.evaluate(function() {