Repository: jupyter/nbgrader (language: python)

Issue:
Nbgrader Menu in JupyterLab opens windows that cannot be closed
### Operating system

MacOS 13.2.1 (22D68)

### `nbgrader --version`

```
% nbgrader --version     
Python version 3.11.1 | packaged by conda-forge | (main, Mar 30 2023, 16:46:19) [Clang 14.0.6 ]
nbgrader version 0.8.2
```

### `jupyter notebook --version` 

Not using jupyter notbook but jupyter lab:

```
% jupyter notebook --version
6.5.3
% jupyter lab --version
3.5.3
```

### Expected behavior

The windows opened by Nbgrader menus (Assignment List, Formgrder, Course List) should be closable.

### Actual behavior

If a window is opened the second time (e.g., Nbgrader > Formgrader, close, Nbgrader > Formgrader), the widow no longer has title and cannot be closed. See the screenshot:

<img width="954" alt="Screenshot 2023-03-31 at 14 16 03" src="https://user-images.githubusercontent.com/2140361/229119116-a981f4ca-dd57-410b-af76-d5ed6e7d09a2.png">


### Steps to reproduce the behavior

1. Install nbgrader and jupyter lab:

```
mamba create -n nbg -c conda-forge python -y
mamba activate nbg
mamba install jupyterlab  
pip install nbgrader # the latest version is not yet on conda-forge
```

2. Setup a course
```
mkdir -p /Users/Shared/nbgrader
cd /Users/Shared/nbgrader
nbgrader quickstart course101
% cat ~/.jupyter/nbgrader_config.py 
c = get_config()
c.Exchange.root = "/tmp/nbgrader/exchange"
c.CourseDirectory.root = "/Users/Shared/nbgrader/course101"
```
3. Start jupter lab
```
% jupyter lab
```

4. Open menu `Nbgrader > Formgrader` (or `Assignment List` or `Course List`)
5. Close the  opened window `Formgrader`
6. Open the same window again: `Nbgrader > Formgrader`
The result is shown in the screenshot above

### Further details

Upon opening the menu second time (Step 6) the browser console (Safari) shows the following messages:

```
[Warning] A disposed object cannot be added. – je {_flags: 5, _layout: null, _parent: null, …} (jlab_core.6144df8e3fe03e33f880.js, line 2)
je {_flags: 5, _layout: null, _parent: null, _disposed: e, _hiddenMode: 0, …}je
[Error] Unhandled Promise Rejection: Error: A disposed object cannot be added.
	(anonymous function) (jlab_core.6144df8e3fe03e33f880.js:2:83513)
	asyncFunctionResume
	(anonymous function)
	promiseReactionJobWithoutPromise
	promiseReactionJob
[Error] TypeError: null is not an object (evaluating 'this.layout.widgets') — jlab_core.6144df8e3fe03e33f880.js:2:1605607
	m (jlab_core.6144df8e3fe03e33f880.js:2:1546745)
	b (jlab_core.6144df8e3fe03e33f880.js:2:1547162)
	t (jlab_core.6144df8e3fe03e33f880.js:2:1545706)
	y (jlab_core.6144df8e3fe03e33f880.js:2:1547390)
```


<details>
<summary>JupyterLab Log</summary>

```                    
I 2023-03-31 14:39:09.777 ServerApp] jupyterlab | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbclassic | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.assignment_list | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.course_list | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.formgrader | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.validate_assignment | extension was successfully linked.
[I 2023-03-31 14:39:09.928 ServerApp] notebook_shim | extension was successfully linked.
[I 2023-03-31 14:39:09.966 ServerApp] notebook_shim | extension was successfully loaded.
[I 2023-03-31 14:39:09.966 LabApp] JupyterLab extension loaded from /opt/homebrew/Caskroom/mambaforge/base/envs/nbg/lib/python3.11/site-packages/jupyterlab
[I 2023-03-31 14:39:09.966 LabApp] JupyterLab application directory is /opt/homebrew/Caskroom/mambaforge/base/envs/nbg/share/jupyter/lab
[I 2023-03-31 14:39:09.967 ServerApp] jupyterlab | extension was successfully loaded.

  _   _          _      _
 | | | |_ __  __| |__ _| |_ ___
 | |_| | '_ \/ _` / _` |  _/ -_)
  \___/| .__/\__,_\__,_|\__\___|
       |_|
                                                                           
Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions.

https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html

Please note that updating to Notebook 7 might break some of your extensions.

[I 2023-03-31 14:39:09.969 ServerApp] nbclassic | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the assignment_list nbgrader serverextension
[I 2023-03-31 14:39:09.970 ServerApp] nbgrader.server_extensions.assignment_list | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the course_list nbgrader serverextension
[I 2023-03-31 14:39:09.970 ServerApp] nbgrader.server_extensions.course_list | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the formgrader nbgrader serverextension
[I 2023-03-31 14:39:09.979 ServerApp] nbgrader.server_extensions.formgrader | extension was successfully loaded.
[I 2023-03-31 14:39:09.979 ServerApp] Loading the validate_assignment nbgrader serverextension
[I 2023-03-31 14:39:09.979 ServerApp] nbgrader.server_extensions.validate_assignment | extension was successfully loaded.
[I 2023-03-31 14:39:09.980 ServerApp] The port 8888 is already in use, trying another port.
[I 2023-03-31 14:39:09.980 ServerApp] Serving notebooks from local directory: /Users/Shared/nbgrader
[I 2023-03-31 14:39:09.980 ServerApp] Jupyter Server 1.23.4 is running at:
[I 2023-03-31 14:39:09.980 ServerApp] http://localhost:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
[I 2023-03-31 14:39:09.980 ServerApp]  or http://127.0.0.1:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
[I 2023-03-31 14:39:09.980 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2023-03-31 14:39:09.983 ServerApp] 
    
    To access the server, open this file in a browser:
        file:///Users/ecull/Library/Jupyter/runtime/jpserver-76178-open.html
    Or copy and paste one of these URLs:
        http://localhost:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
     or http://127.0.0.1:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
This version of python seems to be incorrectly compiled
(internal generated filenames are not absolute).
This may make the debugger miss breakpoints.
Related bug: http://bugs.python.org/issue1666807
[E 2023-03-31 14:39:11.699 ServerApp] Uncaught exception GET /lab/api/settings?1680266351580 (::1)
    HTTPServerRequest(protocol='http', host='localhost:8889', method='GET', uri='/lab/api/settings?1680266351580', version='HTTP/1.1', remote_ip='::1')
    Traceback (most recent call last):
      File "/opt/homebrew/Caskroom/mambaforge/base/envs/nbg/lib/python3.11/site-packages/tornado/web.py", line 1713, in _execute
        result = await result
                 ^^^^^^^^^^^^
    tornado.iostream.StreamClosedError: Stream is closed
[W 2023-03-31 14:39:12.960 LabApp] Could not determine jupyterlab build status without nodejs
[I 2023-03-31 14:41:19.353 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.359 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.362 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.069 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.073 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.076 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.094 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.094 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.095 ServerApp] Released assignments:
``` 
</details>
Nbgrader Menu in JupyterLab opens windows that cannot be closed
### Operating system

MacOS 13.2.1 (22D68)

### `nbgrader --version`

```
% nbgrader --version     
Python version 3.11.1 | packaged by conda-forge | (main, Mar 30 2023, 16:46:19) [Clang 14.0.6 ]
nbgrader version 0.8.2
```

### `jupyter notebook --version` 

Not using jupyter notbook but jupyter lab:

```
% jupyter notebook --version
6.5.3
% jupyter lab --version
3.5.3
```

### Expected behavior

The windows opened by Nbgrader menus (Assignment List, Formgrder, Course List) should be closable.

### Actual behavior

If a window is opened the second time (e.g., Nbgrader > Formgrader, close, Nbgrader > Formgrader), the widow no longer has title and cannot be closed. See the screenshot:

<img width="954" alt="Screenshot 2023-03-31 at 14 16 03" src="https://user-images.githubusercontent.com/2140361/229119116-a981f4ca-dd57-410b-af76-d5ed6e7d09a2.png">


### Steps to reproduce the behavior

1. Install nbgrader and jupyter lab:

```
mamba create -n nbg -c conda-forge python -y
mamba activate nbg
mamba install jupyterlab  
pip install nbgrader # the latest version is not yet on conda-forge
```

2. Setup a course
```
mkdir -p /Users/Shared/nbgrader
cd /Users/Shared/nbgrader
nbgrader quickstart course101
% cat ~/.jupyter/nbgrader_config.py 
c = get_config()
c.Exchange.root = "/tmp/nbgrader/exchange"
c.CourseDirectory.root = "/Users/Shared/nbgrader/course101"
```
3. Start jupter lab
```
% jupyter lab
```

4. Open menu `Nbgrader > Formgrader` (or `Assignment List` or `Course List`)
5. Close the  opened window `Formgrader`
6. Open the same window again: `Nbgrader > Formgrader`
The result is shown in the screenshot above

### Further details

Upon opening the menu second time (Step 6) the browser console (Safari) shows the following messages:

```
[Warning] A disposed object cannot be added. – je {_flags: 5, _layout: null, _parent: null, …} (jlab_core.6144df8e3fe03e33f880.js, line 2)
je {_flags: 5, _layout: null, _parent: null, _disposed: e, _hiddenMode: 0, …}je
[Error] Unhandled Promise Rejection: Error: A disposed object cannot be added.
	(anonymous function) (jlab_core.6144df8e3fe03e33f880.js:2:83513)
	asyncFunctionResume
	(anonymous function)
	promiseReactionJobWithoutPromise
	promiseReactionJob
[Error] TypeError: null is not an object (evaluating 'this.layout.widgets') — jlab_core.6144df8e3fe03e33f880.js:2:1605607
	m (jlab_core.6144df8e3fe03e33f880.js:2:1546745)
	b (jlab_core.6144df8e3fe03e33f880.js:2:1547162)
	t (jlab_core.6144df8e3fe03e33f880.js:2:1545706)
	y (jlab_core.6144df8e3fe03e33f880.js:2:1547390)
```


<details>
<summary>JupyterLab Log</summary>

```                    
I 2023-03-31 14:39:09.777 ServerApp] jupyterlab | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbclassic | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.assignment_list | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.course_list | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.formgrader | extension was successfully linked.
[I 2023-03-31 14:39:09.779 ServerApp] nbgrader.server_extensions.validate_assignment | extension was successfully linked.
[I 2023-03-31 14:39:09.928 ServerApp] notebook_shim | extension was successfully linked.
[I 2023-03-31 14:39:09.966 ServerApp] notebook_shim | extension was successfully loaded.
[I 2023-03-31 14:39:09.966 LabApp] JupyterLab extension loaded from /opt/homebrew/Caskroom/mambaforge/base/envs/nbg/lib/python3.11/site-packages/jupyterlab
[I 2023-03-31 14:39:09.966 LabApp] JupyterLab application directory is /opt/homebrew/Caskroom/mambaforge/base/envs/nbg/share/jupyter/lab
[I 2023-03-31 14:39:09.967 ServerApp] jupyterlab | extension was successfully loaded.

  _   _          _      _
 | | | |_ __  __| |__ _| |_ ___
 | |_| | '_ \/ _` / _` |  _/ -_)
  \___/| .__/\__,_\__,_|\__\___|
       |_|
                                                                           
Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions.

https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html

Please note that updating to Notebook 7 might break some of your extensions.

[I 2023-03-31 14:39:09.969 ServerApp] nbclassic | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the assignment_list nbgrader serverextension
[I 2023-03-31 14:39:09.970 ServerApp] nbgrader.server_extensions.assignment_list | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the course_list nbgrader serverextension
[I 2023-03-31 14:39:09.970 ServerApp] nbgrader.server_extensions.course_list | extension was successfully loaded.
[I 2023-03-31 14:39:09.970 ServerApp] Loading the formgrader nbgrader serverextension
[I 2023-03-31 14:39:09.979 ServerApp] nbgrader.server_extensions.formgrader | extension was successfully loaded.
[I 2023-03-31 14:39:09.979 ServerApp] Loading the validate_assignment nbgrader serverextension
[I 2023-03-31 14:39:09.979 ServerApp] nbgrader.server_extensions.validate_assignment | extension was successfully loaded.
[I 2023-03-31 14:39:09.980 ServerApp] The port 8888 is already in use, trying another port.
[I 2023-03-31 14:39:09.980 ServerApp] Serving notebooks from local directory: /Users/Shared/nbgrader
[I 2023-03-31 14:39:09.980 ServerApp] Jupyter Server 1.23.4 is running at:
[I 2023-03-31 14:39:09.980 ServerApp] http://localhost:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
[I 2023-03-31 14:39:09.980 ServerApp]  or http://127.0.0.1:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
[I 2023-03-31 14:39:09.980 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2023-03-31 14:39:09.983 ServerApp] 
    
    To access the server, open this file in a browser:
        file:///Users/ecull/Library/Jupyter/runtime/jpserver-76178-open.html
    Or copy and paste one of these URLs:
        http://localhost:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
     or http://127.0.0.1:8889/lab?token=85a8ceab9bd094b4b872c6b0efc6c9655c16026124b5c70c
This version of python seems to be incorrectly compiled
(internal generated filenames are not absolute).
This may make the debugger miss breakpoints.
Related bug: http://bugs.python.org/issue1666807
[E 2023-03-31 14:39:11.699 ServerApp] Uncaught exception GET /lab/api/settings?1680266351580 (::1)
    HTTPServerRequest(protocol='http', host='localhost:8889', method='GET', uri='/lab/api/settings?1680266351580', version='HTTP/1.1', remote_ip='::1')
    Traceback (most recent call last):
      File "/opt/homebrew/Caskroom/mambaforge/base/envs/nbg/lib/python3.11/site-packages/tornado/web.py", line 1713, in _execute
        result = await result
                 ^^^^^^^^^^^^
    tornado.iostream.StreamClosedError: Stream is closed
[W 2023-03-31 14:39:12.960 LabApp] Could not determine jupyterlab build status without nodejs
[I 2023-03-31 14:41:19.353 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.359 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.362 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:41:19.433 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.069 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.073 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.076 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.094 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.094 ServerApp] Released assignments:
[I 2023-03-31 14:42:17.095 ServerApp] Released assignments:
``` 
</details>


Discussion / hints:
A related issue for the JupyterLab extension tutorial:
https://github.com/jupyterlab/jupyterlab_apod/issues/3

The proposed solution is to recreate a widget if it is disposed:
https://github.com/jupyterlab/jupyterlab_apod/issues/3#issuecomment-638437677
A related issue for the JupyterLab extension tutorial:
https://github.com/jupyterlab/jupyterlab_apod/issues/3

The proposed solution is to recreate a widget if it is disposed:
https://github.com/jupyterlab/jupyterlab_apod/issues/3#issuecomment-638437677

Fix patch:
diff --git a/src/assignment_list/index.ts b/src/assignment_list/index.ts
index a9b07778a..ee959bbe3 100644
--- a/src/assignment_list/index.ts
+++ b/src/assignment_list/index.ts
@@ -192,7 +192,7 @@ export const assignment_list_extension: JupyterFrontEndPlugin<void> = {
     app.commands.addCommand(command,{
       label: 'Assignment List',
       execute: () => {
-        if(!widget){
+        if(!widget || widget.isDisposed){
           const content = new AssignmentListWidget(app);
           widget = new MainAreaWidget({content});
           widget.id = 'nbgrader-assignment-list';
diff --git a/src/course_list/index.ts b/src/course_list/index.ts
index 562ea9588..9cda70337 100644
--- a/src/course_list/index.ts
+++ b/src/course_list/index.ts
@@ -123,7 +123,7 @@ export const course_list_extension: JupyterFrontEndPlugin<void> = {
         app.commands.addCommand(command, {
             label: 'Course List',
             execute: () => {
-                if (!widget) {
+                if (!widget || widget.isDisposed) {
                     const content = new CourseListWidget(app);
                     widget = new MainAreaWidget({content});
                     widget.id = 'nbgrader-course-list'
diff --git a/src/formgrader/index.ts b/src/formgrader/index.ts
index c7772a13b..a21994584 100644
--- a/src/formgrader/index.ts
+++ b/src/formgrader/index.ts
@@ -78,7 +78,7 @@ export const formgrader_extension: JupyterFrontEndPlugin<void> = {
     app.commands.addCommand(command,{
     label: 'Formgrader',
     execute: async args => {
-      if(!widget){
+      if(!widget || widget.isDisposed){
         const settings = ServerConnection.makeSettings();
         const url = (args.url as string) || URLExt.join(settings.baseUrl, "formgrader");
 


Test patch:
diff --git a/nbgrader/tests/labextension_ui-tests/tests/test_assignment_list.spec.ts b/nbgrader/tests/labextension_ui-tests/tests/test_assignment_list.spec.ts
index 3b98cce16..867172857 100644
--- a/nbgrader/tests/labextension_ui-tests/tests/test_assignment_list.spec.ts
+++ b/nbgrader/tests/labextension_ui-tests/tests/test_assignment_list.spec.ts
@@ -156,19 +156,33 @@ test('Open assignment list tab from menu', async({
   page
   }) => {
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(1);
-    // Check main menu exists
-    await expect(page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")')).toHaveCount(1);
-    await page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")').click();
+    const nbgrader_menu = page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")');
+    const assignmentList_menu = page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-assignment-list"]');
+    const tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
+    const lastTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
 
-    await page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-assignment-list"]').click();
+    await expect(tabs).toHaveCount(1);
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(2);
+    // Check main menu exists    
+    await expect(nbgrader_menu).toHaveCount(1);
 
-    var tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
-    var newTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
-    await expect(newTab_label).toHaveText("Assignments");
+    // Open assignment list from the menu
+    await nbgrader_menu.click();    
+    await assignmentList_menu.click();
 
+    await expect(tabs).toHaveCount(2);        
+    await expect(lastTab_label).toHaveText("Assignments");
+
+    // Close the last tab
+    await tabs.last().locator(".jp-icon-hover.lm-TabBar-tabCloseIcon").click();
+    await expect(tabs).toHaveCount(1);
+
+    // Open again
+    await nbgrader_menu.click();
+    await assignmentList_menu.click();
+
+    await expect(tabs).toHaveCount(2);
+    await expect(lastTab_label).toHaveText("Assignments");
 });
 
 /*
diff --git a/nbgrader/tests/labextension_ui-tests/tests/test_course_list.spec.ts b/nbgrader/tests/labextension_ui-tests/tests/test_course_list.spec.ts
index 8ce62d49e..008d3ea0c 100644
--- a/nbgrader/tests/labextension_ui-tests/tests/test_course_list.spec.ts
+++ b/nbgrader/tests/labextension_ui-tests/tests/test_course_list.spec.ts
@@ -103,19 +103,33 @@ test('Open course list tab from menu', async({
   page
   }) => {
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(1);
+    const nbgrader_menu = page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")');
+    const courseList_menu = page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-course-list"]');
+    const tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
+    const lastTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
+
+    await expect(tabs).toHaveCount(1);
 
     // Check main menu exists
-    await expect(page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")')).toHaveCount(1);
-    await page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")').click();
+    await expect(nbgrader_menu).toHaveCount(1);
 
-    await page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-course-list"]').click();
+    // Open course list from the menu
+    await nbgrader_menu.click();    
+    await courseList_menu.click();
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(2);
+    await expect(tabs).toHaveCount(2);        
+    await expect(lastTab_label).toHaveText("Courses");
 
-    var tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
-    var newTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
-    await expect(newTab_label).toHaveText("Courses");
+    // Close the last tab
+    await tabs.last().locator(".jp-icon-hover.lm-TabBar-tabCloseIcon").click();
+    await expect(tabs).toHaveCount(1);
+
+    // Open again
+    await nbgrader_menu.click();
+    await courseList_menu.click();
+
+    await expect(tabs).toHaveCount(2);
+    await expect(lastTab_label).toHaveText("Courses");
 
 });
 
diff --git a/nbgrader/tests/labextension_ui-tests/tests/test_formgrader.spec.ts b/nbgrader/tests/labextension_ui-tests/tests/test_formgrader.spec.ts
index 6650d0278..489fa0481 100644
--- a/nbgrader/tests/labextension_ui-tests/tests/test_formgrader.spec.ts
+++ b/nbgrader/tests/labextension_ui-tests/tests/test_formgrader.spec.ts
@@ -197,18 +197,33 @@ test('Open assignment list tab from menu', async({
   page
   }) => {
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(1);
+    const nbgrader_menu = page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")');
+    const formgrader_menu = page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-formgrader"]');
+    const tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
+    const lastTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
+
+    await expect(tabs).toHaveCount(1);
+
     // Check main menu exists
-    await expect(page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")')).toHaveCount(1);
-    await page.locator('#jp-menu-panel div.lm-MenuBar-itemLabel.p-MenuBar-itemLabel:text("Nbgrader")').click();
+    await expect(nbgrader_menu).toHaveCount(1);
+
+    // Open course list from the menu
+    await nbgrader_menu.click();    
+    await formgrader_menu.click();
+
+    await expect(tabs).toHaveCount(2);        
+    await expect(lastTab_label).toHaveText("Formgrader");
 
-    await page.locator('#jp-mainmenu-nbgrader-menu li[data-command="nbgrader:open-formgrader"]').click();
+    // Close the last tab
+    await tabs.last().locator(".jp-icon-hover.lm-TabBar-tabCloseIcon").click();
+    await expect(tabs).toHaveCount(1);
 
-    await expect(page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab")).toHaveCount(2);
+    // Open again
+    await nbgrader_menu.click();
+    await formgrader_menu.click();
 
-    var tabs = page.locator("#jp-main-dock-panel .lm-TabBar-tab.p-TabBar-tab");
-    var newTab_label = tabs.last().locator(".lm-TabBar-tabLabel.p-TabBar-tabLabel");
-    await expect(newTab_label).toHaveText("Formgrader");
+    await expect(tabs).toHaveCount(2);
+    await expect(lastTab_label).toHaveText("Formgrader");
 
 });