@jupyterlab/galata 5.0.0-alpha.2 → 5.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +192 -31
  2. package/lib/benchmarkReporter.d.ts +1 -0
  3. package/lib/benchmarkReporter.js +34 -39
  4. package/lib/benchmarkReporter.js.map +1 -1
  5. package/lib/benchmarkVLTpl.js +19 -5
  6. package/lib/benchmarkVLTpl.js.map +1 -1
  7. package/lib/contents.d.ts +5 -5
  8. package/lib/contents.js +32 -36
  9. package/lib/contents.js.map +1 -1
  10. package/lib/extension/global.d.ts +197 -0
  11. package/lib/extension/global.js +601 -0
  12. package/lib/extension/global.js.map +1 -0
  13. package/lib/extension/index.d.ts +6 -0
  14. package/lib/extension/index.js +27 -0
  15. package/lib/extension/index.js.map +1 -0
  16. package/lib/extension/tokens.d.ts +232 -0
  17. package/lib/extension/tokens.js +13 -0
  18. package/lib/extension/tokens.js.map +1 -0
  19. package/lib/extension.d.ts +223 -0
  20. package/lib/{global.js → extension.js} +1 -2
  21. package/lib/extension.js.map +1 -0
  22. package/lib/fixtures.d.ts +32 -10
  23. package/lib/fixtures.js +64 -17
  24. package/lib/fixtures.js.map +1 -1
  25. package/lib/galata.d.ts +140 -19
  26. package/lib/galata.js +272 -87
  27. package/lib/galata.js.map +1 -1
  28. package/lib/helpers/activity.d.ts +6 -0
  29. package/lib/helpers/activity.js +19 -5
  30. package/lib/helpers/activity.js.map +1 -1
  31. package/lib/helpers/debuggerpanel.d.ts +4 -0
  32. package/lib/helpers/debuggerpanel.js +16 -0
  33. package/lib/helpers/debuggerpanel.js.map +1 -1
  34. package/lib/helpers/filebrowser.js +8 -2
  35. package/lib/helpers/filebrowser.js.map +1 -1
  36. package/lib/helpers/index.d.ts +1 -0
  37. package/lib/helpers/index.js +6 -1
  38. package/lib/helpers/index.js.map +1 -1
  39. package/lib/helpers/kernel.js +7 -7
  40. package/lib/helpers/kernel.js.map +1 -1
  41. package/lib/helpers/menu.d.ts +7 -0
  42. package/lib/helpers/menu.js +17 -1
  43. package/lib/helpers/menu.js.map +1 -1
  44. package/lib/helpers/notebook.d.ts +6 -4
  45. package/lib/helpers/notebook.js +127 -31
  46. package/lib/helpers/notebook.js.map +1 -1
  47. package/lib/helpers/sidebar.d.ts +8 -1
  48. package/lib/helpers/sidebar.js +33 -15
  49. package/lib/helpers/sidebar.js.map +1 -1
  50. package/lib/helpers/statusbar.js +1 -1
  51. package/lib/helpers/statusbar.js.map +1 -1
  52. package/lib/helpers/style.d.ts +42 -0
  53. package/lib/helpers/style.js +50 -0
  54. package/lib/helpers/style.js.map +1 -0
  55. package/lib/helpers/theme.js +1 -1
  56. package/lib/helpers/theme.js.map +1 -1
  57. package/lib/index.d.ts +5 -2
  58. package/lib/index.js +12 -3
  59. package/lib/index.js.map +1 -1
  60. package/lib/jupyterlabpage.d.ts +29 -4
  61. package/lib/jupyterlabpage.js +38 -22
  62. package/lib/jupyterlabpage.js.map +1 -1
  63. package/lib/playwright-config.js +5 -1
  64. package/lib/playwright-config.js.map +1 -1
  65. package/lib/utils.js +5 -1
  66. package/lib/utils.js.map +1 -1
  67. package/package.json +31 -47
  68. package/src/benchmarkReporter.ts +756 -0
  69. package/src/benchmarkVLTpl.ts +91 -0
  70. package/src/contents.ts +472 -0
  71. package/src/extension.ts +281 -0
  72. package/src/fixtures.ts +387 -0
  73. package/src/galata.ts +1035 -0
  74. package/src/helpers/activity.ts +115 -0
  75. package/src/helpers/debuggerpanel.ts +159 -0
  76. package/src/helpers/filebrowser.ts +228 -0
  77. package/src/helpers/index.ts +15 -0
  78. package/src/helpers/kernel.ts +39 -0
  79. package/src/helpers/logconsole.ts +32 -0
  80. package/src/helpers/menu.ts +228 -0
  81. package/src/helpers/notebook.ts +1217 -0
  82. package/src/helpers/performance.ts +57 -0
  83. package/src/helpers/sidebar.ts +289 -0
  84. package/src/helpers/statusbar.ts +56 -0
  85. package/src/helpers/style.ts +100 -0
  86. package/src/helpers/theme.ts +50 -0
  87. package/src/index.ts +19 -0
  88. package/src/jupyterlabpage.ts +704 -0
  89. package/src/playwright-config.ts +26 -0
  90. package/src/utils.ts +264 -0
  91. package/src/vega-statistics.d.ts +15 -0
  92. package/lib/global.d.ts +0 -23
  93. package/lib/global.js.map +0 -1
  94. package/lib/inpage/tokens.d.ts +0 -135
  95. package/lib/inpage/tokens.js +0 -9
  96. package/lib/inpage/tokens.js.map +0 -1
  97. package/lib/lib-inpage/inpage.js +0 -3957
  98. package/lib/lib-inpage/inpage.js.map +0 -1
  99. package/style/index.css +0 -10
  100. package/style/index.js +0 -10
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { ElementHandle, Page } from '@playwright/test';
5
+ import * as Utils from '../utils';
6
+
7
+ /**
8
+ * Activity helper
9
+ */
10
+ export class ActivityHelper {
11
+ constructor(readonly page: Page) {}
12
+
13
+ /**
14
+ * JupyterLab launcher selector
15
+ */
16
+ get launcherSelector(): string {
17
+ return Utils.xpBuildActivityTabSelector('Launcher');
18
+ }
19
+
20
+ /**
21
+ * Close all widgets in the main area
22
+ */
23
+ async closeAll(): Promise<void> {
24
+ await this.page.evaluate(async (launcherSelector: string) => {
25
+ await window.jupyterapp.commands.execute('application:close-all');
26
+ await window.galata.waitForXPath(launcherSelector);
27
+ }, this.launcherSelector);
28
+ }
29
+
30
+ /**
31
+ * Whether a tab is active or not
32
+ *
33
+ * @param name Activity name
34
+ * @returns Active status
35
+ */
36
+ async isTabActive(name: string): Promise<boolean> {
37
+ const tab = await this.getTab(name);
38
+ return (
39
+ (tab &&
40
+ (await tab.evaluate((tab: Element) =>
41
+ tab.classList.contains('lm-mod-current')
42
+ ))) ??
43
+ false
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Get a handle on a tab
49
+ *
50
+ * @param name Activity name
51
+ * @returns Handle on the tab or null if the tab is not found
52
+ */
53
+ getTab(name?: string): Promise<ElementHandle<Element> | null> {
54
+ const page = this.page;
55
+ const tabSelector = name
56
+ ? Utils.xpBuildActivityTabSelector(name)
57
+ : Utils.xpBuildActiveActivityTabSelector();
58
+ return page.$(`xpath=${tabSelector}`);
59
+ }
60
+
61
+ /**
62
+ * Get a handle on a panel
63
+ *
64
+ * @param name Activity name
65
+ * @returns Handle on the tab or null if the tab is not found
66
+ */
67
+ async getPanel(name?: string): Promise<ElementHandle<Element> | null> {
68
+ const page = this.page;
69
+ const tab = await this.getTab(name);
70
+ if (tab) {
71
+ const id = await tab.evaluate((tab: Element) =>
72
+ tab.getAttribute('data-id')
73
+ );
74
+ return await page.$(`xpath=${Utils.xpBuildActivityPanelSelector(id!)}`);
75
+ }
76
+
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * Close a panel from its tab name
82
+ *
83
+ * @param name Activity name
84
+ */
85
+ async closePanel(name: string): Promise<void> {
86
+ await this.activateTab(name);
87
+ await this.page.evaluate(async (launcherSelector: string) => {
88
+ await window.jupyterapp.commands.execute('application:close');
89
+ await window.galata.waitForXPath(launcherSelector);
90
+ }, this.launcherSelector);
91
+ }
92
+
93
+ /**
94
+ * Activate a tab is active
95
+ *
96
+ * @param name Activity name
97
+ * @returns Whether the action is successful
98
+ */
99
+ async activateTab(name: string): Promise<boolean> {
100
+ const tab = await this.getTab(name);
101
+ if (tab) {
102
+ await tab.click();
103
+ await this.page.waitForFunction(
104
+ ({ tab }) => {
105
+ return tab.classList.contains('jp-mod-current');
106
+ },
107
+ { tab }
108
+ );
109
+
110
+ return true;
111
+ }
112
+
113
+ return false;
114
+ }
115
+ }
@@ -0,0 +1,159 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { ElementHandle, Page } from '@playwright/test';
7
+ import { SidebarHelper } from './sidebar';
8
+ import { NotebookHelper } from './notebook';
9
+ import { waitForCondition } from '../utils';
10
+
11
+ const DEBUGGER_ITEM = 'debugger-icon';
12
+
13
+ /**
14
+ * Debugger Helper
15
+ */
16
+ export class DebuggerHelper {
17
+ constructor(
18
+ readonly page: Page,
19
+ readonly sidebar: SidebarHelper,
20
+ readonly notebook: NotebookHelper
21
+ ) {}
22
+
23
+ /**
24
+ * Returns true if debugger toolbar item is enabled, false otherwise
25
+ */
26
+ async isOn(): Promise<boolean> {
27
+ if (!(await this.notebook.isAnyActive())) {
28
+ return false;
29
+ }
30
+ const item = await this.notebook.getToolbarItem(DEBUGGER_ITEM);
31
+ if (item) {
32
+ const button = await item.$('button');
33
+ if (button) {
34
+ return (await button.getAttribute('aria-pressed')) === 'true';
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Enables the debugger toolbar item
42
+ */
43
+ async switchOn(): Promise<void> {
44
+ await waitForCondition(async () => {
45
+ const item = await this.notebook.getToolbarItem(DEBUGGER_ITEM);
46
+ if (item) {
47
+ const button = await item.$('button');
48
+ if (button) {
49
+ return (await button.getAttribute('aria-disabled')) !== 'true';
50
+ }
51
+ }
52
+ return false;
53
+ }, 2000);
54
+ if (!(await this.isOn())) {
55
+ await this.notebook.clickToolbarItem(DEBUGGER_ITEM);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Disables the debugger toolbar item
61
+ */
62
+ async switchOff(): Promise<void> {
63
+ if (await this.isOn()) {
64
+ await this.notebook.clickToolbarItem(DEBUGGER_ITEM);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Returns true if debugger panel is open, false otherwise
70
+ */
71
+ async isOpen(): Promise<boolean> {
72
+ return await this.sidebar.isTabOpen('jp-debugger-sidebar');
73
+ }
74
+
75
+ /**
76
+ * Returns handle to the variables panel content
77
+ */
78
+ async getVariablesPanel(): Promise<ElementHandle<Element> | null> {
79
+ return this._getPanel('.jp-DebuggerVariables');
80
+ }
81
+
82
+ /**
83
+ * Waits for variables to be populated in the variables panel
84
+ */
85
+ async waitForVariables(): Promise<void> {
86
+ await this.page.waitForSelector('.jp-DebuggerVariables-body ul');
87
+ }
88
+
89
+ /**
90
+ * render variable
91
+ */
92
+ async renderVariable(name: string): Promise<void> {
93
+ await this.page
94
+ .locator(`.jp-DebuggerVariables :text("${name}")`)
95
+ .click({ button: 'right' });
96
+ await this.page
97
+ .locator('.lm-Menu-itemLabel:text("Render Variable")')
98
+ .click();
99
+ await this.page.waitForSelector('.jp-VariableRendererPanel-renderer');
100
+ }
101
+
102
+ /**
103
+ * Returns handle to callstack panel content
104
+ */
105
+ async getCallStackPanel(): Promise<ElementHandle<Element> | null> {
106
+ return this._getPanel('.jp-DebuggerCallstack');
107
+ }
108
+
109
+ /**
110
+ * Waits for the callstack body to populate in the callstack panel
111
+ */
112
+ async waitForCallStack(): Promise<void> {
113
+ await this.page.waitForSelector(
114
+ '.jp-DebuggerCallstack-body >> .jp-DebuggerCallstackFrame'
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Returns handle to breakpoints panel content
120
+ */
121
+ async getBreakPointsPanel(): Promise<ElementHandle<Element> | null> {
122
+ return this._getPanel('.jp-DebuggerBreakpoints');
123
+ }
124
+
125
+ /**
126
+ * Waits for the breakpoints to appear in the breakpoints panel
127
+ */
128
+ async waitForBreakPoints(): Promise<void> {
129
+ await this.page.waitForSelector(
130
+ '.jp-DebuggerBreakpoints >> .jp-DebuggerBreakpoint'
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Returns handle to sources panel content
136
+ */
137
+ async getSourcePanel(): Promise<ElementHandle<Element> | null> {
138
+ return this._getPanel('.jp-DebuggerSources');
139
+ }
140
+
141
+ /**
142
+ * Waits for sources to be populated in the sources panel
143
+ */
144
+ async waitForSources(): Promise<void> {
145
+ await this.page.waitForSelector('.jp-DebuggerSources-body >> .jp-Editor', {
146
+ state: 'visible'
147
+ });
148
+ }
149
+
150
+ private async _getPanel(
151
+ selector: string
152
+ ): Promise<ElementHandle<Element> | null> {
153
+ const panel = await this.sidebar.getContentPanel('right');
154
+ if (panel) {
155
+ return panel.$(selector);
156
+ }
157
+ return null;
158
+ }
159
+ }
@@ -0,0 +1,228 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { Page } from '@playwright/test';
5
+ import * as path from 'path';
6
+ import { ContentsHelper } from '../contents';
7
+ import * as Utils from '../utils';
8
+
9
+ /**
10
+ * File Browser Helpers
11
+ */
12
+ export class FileBrowserHelper {
13
+ constructor(readonly page: Page, readonly contents: ContentsHelper) {}
14
+
15
+ /**
16
+ * Create the selector for a file in the file browser
17
+ *
18
+ * @param fileName File name
19
+ * @returns XPath to file in file browser
20
+ */
21
+ xpBuildFileSelector(fileName: string): string {
22
+ return `//div[@id='filebrowser']//li[./span[${Utils.xpContainsClass(
23
+ 'jp-DirListing-itemText'
24
+ )} and ./span[text()="${fileName}"]]]`;
25
+ }
26
+
27
+ /**
28
+ * Create the selector for a directory in the file browser
29
+ *
30
+ * @param dirName Directory name
31
+ * @returns XPath to directory in file browser
32
+ */
33
+ xpBuildDirectorySelector(dirName: string): string {
34
+ return `//div[@id='filebrowser']//li[@data-isdir='true' and ./span[${Utils.xpContainsClass(
35
+ 'jp-DirListing-itemText'
36
+ )} and ./span[text()="${dirName}"]]]`;
37
+ }
38
+
39
+ /**
40
+ * Reveal a file in the file browser.
41
+ *
42
+ * It will open intermediate folders if needed.
43
+ *
44
+ * @param filePath File path
45
+ */
46
+ async revealFileInBrowser(filePath: string): Promise<void> {
47
+ const pos = filePath.lastIndexOf('/');
48
+ const fileName = path.basename(filePath);
49
+ if (pos >= 0) {
50
+ const dirPath = filePath.substring(0, pos);
51
+ await this.openDirectory(dirPath);
52
+ }
53
+
54
+ await Utils.waitForCondition(async () => {
55
+ return await this.isFileListedInBrowser(fileName);
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Whether the file is listed in the file browser or not.
61
+ *
62
+ * @param fileName File name
63
+ * @returns File status
64
+ */
65
+ async isFileListedInBrowser(fileName: string): Promise<boolean> {
66
+ const item = await this.page.$(
67
+ `xpath=${this.xpBuildFileSelector(fileName)}`
68
+ );
69
+ return item !== null;
70
+ }
71
+
72
+ /**
73
+ * Get the full path of the currently opened directory
74
+ *
75
+ * @returns Directory full path
76
+ */
77
+ async getCurrentDirectory(): Promise<string> {
78
+ return await this.page.evaluate(() => {
79
+ let directory = '';
80
+ const spans = document.querySelectorAll(
81
+ '.jp-FileBrowser .jp-FileBrowser-crumbs span'
82
+ );
83
+ const numSpans = spans.length;
84
+ if (numSpans > 1) {
85
+ directory = spans[numSpans - 2].getAttribute('title') ?? '';
86
+ }
87
+
88
+ return directory;
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Open a file
94
+ *
95
+ * Note: This will double click on the file;
96
+ * an editor needs to be available for the given file type.
97
+ *
98
+ * @param filePath Notebook path
99
+ * @returns Action success status
100
+ */
101
+ async open(filePath: string): Promise<boolean> {
102
+ await this.revealFileInBrowser(filePath);
103
+ const name = path.basename(filePath);
104
+
105
+ const fileItem = await this.page.$(
106
+ `xpath=${this.xpBuildFileSelector(name)}`
107
+ );
108
+ if (fileItem) {
109
+ await fileItem.click({ clickCount: 2 });
110
+ await this.page.waitForSelector(Utils.xpBuildActivityTabSelector(name), {
111
+ state: 'visible'
112
+ });
113
+ } else {
114
+ return false;
115
+ }
116
+
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Open the Home directory.
122
+ *
123
+ * @returns Action success status
124
+ */
125
+ async openHomeDirectory(): Promise<boolean> {
126
+ const homeButton = await this.page.$(
127
+ '.jp-FileBrowser .jp-FileBrowser-crumbs span'
128
+ );
129
+ if (!homeButton) {
130
+ return false;
131
+ }
132
+ await homeButton.click();
133
+
134
+ await this.page.waitForFunction(() => {
135
+ const spans = document.querySelectorAll(
136
+ '.jp-FileBrowser .jp-FileBrowser-crumbs span'
137
+ );
138
+ return (
139
+ // The home is the root if no preferred dir is defined.
140
+ spans.length === 2 && spans[0].classList.contains('jp-BreadCrumbs-home')
141
+ );
142
+ });
143
+
144
+ // wait for DOM rerender
145
+ await this.page.waitForTimeout(200);
146
+
147
+ return true;
148
+ }
149
+
150
+ /**
151
+ * Open a given directory in the file browser
152
+ *
153
+ * @param dirPath Directory path
154
+ * @returns Action success status
155
+ */
156
+ async openDirectory(dirPath: string): Promise<boolean> {
157
+ if (!(await this.openHomeDirectory())) {
158
+ return false;
159
+ }
160
+
161
+ const directories = dirPath.split('/');
162
+ let path = '';
163
+
164
+ for (const directory of directories) {
165
+ if (directory.trim() === '') {
166
+ continue;
167
+ }
168
+ if (path !== '') {
169
+ path += '/';
170
+ }
171
+
172
+ path += directory;
173
+
174
+ if (!(await this._openDirectory(directory))) {
175
+ return false;
176
+ }
177
+
178
+ await Utils.waitForCondition(async () => {
179
+ return (await this.getCurrentDirectory()) === path;
180
+ });
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Trigger a file browser refresh
188
+ */
189
+ async refresh(): Promise<void> {
190
+ const page = this.page;
191
+ const item = await page.$(
192
+ `xpath=//div[@id='filebrowser']//button[${Utils.xpContainsClass(
193
+ 'jp-ToolbarButtonComponent'
194
+ )} and .//*[@data-icon='ui-components:refresh']]`
195
+ );
196
+
197
+ if (item) {
198
+ // wait for network response or timeout
199
+ await Promise.race([
200
+ page.waitForTimeout(2000),
201
+ this.contents.waitForAPIResponse(async () => {
202
+ await item.click();
203
+ })
204
+ ]);
205
+ // wait for DOM rerender
206
+ await page.waitForTimeout(200);
207
+ } else {
208
+ throw new Error('Could not find refresh toolbar item');
209
+ }
210
+ }
211
+
212
+ protected async _openDirectory(dirName: string): Promise<boolean> {
213
+ const item = await this.page.$(
214
+ `xpath=${this.xpBuildDirectorySelector(dirName)}`
215
+ );
216
+ if (item === null) {
217
+ return false;
218
+ }
219
+
220
+ await this.contents.waitForAPIResponse(async () => {
221
+ await item.click({ clickCount: 2 });
222
+ });
223
+ // wait for DOM rerender
224
+ await this.page.waitForTimeout(200);
225
+
226
+ return true;
227
+ }
228
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ export * from './activity';
5
+ export * from './filebrowser';
6
+ export * from './kernel';
7
+ export * from './logconsole';
8
+ export * from './menu';
9
+ export * from './notebook';
10
+ export * from './performance';
11
+ export * from './sidebar';
12
+ export * from './statusbar';
13
+ export * from './style';
14
+ export * from './theme';
15
+ export * from './debuggerpanel';
@@ -0,0 +1,39 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { Page } from '@playwright/test';
5
+ import * as Utils from '../utils';
6
+
7
+ /**
8
+ * Kernels and sessions helpers
9
+ *
10
+ * These helpers are using JupyterLab serviceManager in Javascript. There
11
+ * are therefore not available if the page is not loaded.
12
+ */
13
+ export class KernelHelper {
14
+ constructor(readonly page: Page) {}
15
+
16
+ /**
17
+ * Whether a sessions is running or not.
18
+ *
19
+ * @returns Running status
20
+ */
21
+ async isAnyRunning(): Promise<boolean> {
22
+ return await this.page.evaluate(() => {
23
+ return !window.jupyterapp.serviceManager.sessions.running().next().done;
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Shutdown all sessions.
29
+ */
30
+ async shutdownAll(): Promise<void> {
31
+ await this.page.evaluate(async () => {
32
+ await window.jupyterapp.serviceManager.sessions.shutdownAll();
33
+ });
34
+
35
+ await Utils.waitForCondition(async () => {
36
+ return (await this.isAnyRunning()) === false;
37
+ });
38
+ }
39
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { Page } from '@playwright/test';
5
+
6
+ /**
7
+ * LogConsole helpers
8
+ */
9
+ export class LogConsoleHelper {
10
+ constructor(readonly page: Page) {}
11
+
12
+ /**
13
+ * Get the number of log messages in the log console panel.
14
+ *
15
+ * @returns Number of log messages
16
+ */
17
+ async logCount(): Promise<number> {
18
+ return await this.page.evaluate(() => {
19
+ let count = 0;
20
+ const logPanels = document.querySelectorAll(
21
+ '.jp-LogConsolePanel .lm-StackedPanel-child'
22
+ );
23
+ logPanels.forEach(logPanel => {
24
+ if (!logPanel.classList.contains('lm-mod-hidden')) {
25
+ count += logPanel.querySelectorAll('.jp-OutputArea-child').length;
26
+ }
27
+ });
28
+
29
+ return count;
30
+ });
31
+ }
32
+ }