Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Debugger scenario #25

Merged
merged 3 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/dramaturg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
*/
import { getKeyboardLayout } from '@lumino/keyboard';

export async function waitUntilDisappears(selector: string) {
return new Promise<void>((accept, reject) => {
const timeout = 5 * 1000;
const step = 50;
let total = 0;
const id = setInterval(() => {
const match = document.querySelector(selector);
if (!match) {
clearInterval(id);
return accept();
} else if (total > timeout) {
clearInterval(id);
return reject(`${selector} did not disappear in ${timeout}ms`);
}
total += step;
}, step);
});
}

function waitElementVisible(
selector: string,
within?: Element,
Expand Down Expand Up @@ -283,7 +302,9 @@ async function click(element: HTMLElement) {
const rect = element.getBoundingClientRect();
const initDict = {
clientX: rect.x + rect.width / 2,
clientY: rect.x + rect.height / 2
clientY: rect.x + rect.height / 2,
// bubbles required for React.js
bubbles: true
};
element.dispatchEvent(new MouseEvent('mousedown', initDict));
element.dispatchEvent(new MouseEvent('mouseup', initDict));
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
SwitchTabFocusScenario,
SidebarOpenScenario,
CompleterScenario,
ScrollScenario
ScrollScenario,
DebuggerScenario
} from './scenarios';
import {
executionTimeBenchmark,
Expand Down Expand Up @@ -70,7 +71,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
new SwitchTabFocusScenario(app),
new SidebarOpenScenario(app),
new CompleterScenario(app),
new ScrollScenario(app)
new ScrollScenario(app),
new DebuggerScenario(app)
],
translator: nullTranslator,
upload: (file: File) => {
Expand Down
139 changes: 133 additions & 6 deletions src/scenarios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
page,
layoutReady,
ElementHandle,
waitForScrollEnd
waitForScrollEnd,
waitUntilDisappears
} from './dramaturg';
import { IScenario } from './benchmark';

Expand All @@ -17,11 +18,13 @@ import type { MenuOpenScenarioOptions } from './types/_scenario-menu-open';
import type { CompleterScenarioOptions } from './types/_scenario-completer';
import type { SidebarsScenarioOptions } from './types/_scenario-sidebars';
import type { ScrollScenarioOptions } from './types/_scenario-scroll';
import type { DebuggerScenarioOptions } from './types/_scenario-debugger';

import scenarioOptionsSchema from './schema/scenario-base.json';
import scenarioMenuOpenOptionsSchema from './schema/scenario-menu-open.json';
import scenarioTabOptionsSchema from './schema/scenario-tabs.json';
import scenarioCompleterOptionsSchema from './schema/scenario-completer.json';
import scenarioDebuggerOptionsSchema from './schema/scenario-debugger.json';
import scenarioSidebarsSchema from './schema/scenario-sidebars.json';
import scenarioScrollSchema from './schema/scenario-scroll.json';

Expand Down Expand Up @@ -142,8 +145,16 @@ export function insertText(
});
}

interface IExtendedDebuggerScenarioOptions extends DebuggerScenarioOptions {
editor: 'Notebook';
path: null;
}

class SingleEditorScenario<
T extends CompleterScenarioOptions | ScrollScenarioOptions
T extends
| CompleterScenarioOptions
| ScrollScenarioOptions
| IExtendedDebuggerScenarioOptions
> {
constructor(protected jupyterApp: JupyterFrontEnd) {
// no-op
Expand Down Expand Up @@ -242,10 +253,7 @@ export class CompleterScenario
}
if (this.useNotebook && this.options.setup.setupCell) {
await insertText(this.jupyterApp, this.options.setup.setupCell);
await page.waitForSelector(
'.jp-Notebook-ExecutionIndicator[data-status="idle"]',
{ state: 'attached' }
);
await waitForKernelStatus(this.widget.node, 'idle');
await this.jupyterApp.commands.execute(
'notebook:run-cell-and-insert-below'
);
Expand Down Expand Up @@ -318,6 +326,125 @@ export class CompleterScenario
}
}

export class DebuggerScenario
extends SingleEditorScenario<IExtendedDebuggerScenarioOptions>
implements IScenario
{
id = 'debugger';
name = 'Debugger';
configSchema = scenarioDebuggerOptionsSchema as any as JSONSchema7;

setOptions(options: DebuggerScenarioOptions): void {
super.setOptions({
...options,
editor: 'Notebook',
path: null
});
}

private async _goToTop() {
if (!this.options) {
throw new Error('Setup failure');
}
for (let i = 0; i < this.options.codeCells.length + 1; i++) {
await this.jupyterApp.commands.execute('notebook:move-cursor-up');
}
}

async setupSuite(): Promise<void> {
await super.setupSuite();
if (!this.widget || !this.options) {
throw new Error('Parent setup failure');
}

for (const codeCell of this.options.codeCells) {
await insertText(this.jupyterApp, codeCell);
await this.jupyterApp.commands.execute('notebook:insert-cell-below');
await layoutReady();
await layoutReady();
await this.jupyterApp.commands.execute('notebook:enter-edit-mode');
await layoutReady();
await layoutReady();
}
await insertText(this.jupyterApp, '%reset -f');
await this._goToTop();

await waitForKernelStatus(this.widget.node, 'idle');
const handle = new ElementHandle(this.widget.node);
const bugIcon = await handle.waitForSelector(
'button[aria-disabled="false"][title="Enable Debugger"]',
{
state: 'attached'
}
);
await bugIcon.click();
await page.waitForSelector(`#${CSS.escape('jp-debugger-sidebar')}`, {
state: 'visible'
});
await handle.waitForSelector(
'button[aria-disabled="false"][title="Disable Debugger"]',
{
state: 'attached'
}
);
await layoutReady();

await page.waitForSelector('.jp-DebuggerVariables-body', {
state: 'attached'
});
await page.waitForSelector('.jp-DebuggerVariables-body', {
state: 'visible'
});
}

async run(): Promise<void> {
if (!this.widget || !this.options) {
throw new Error('Setup failure');
}

for (let i = 0; i < this.options.codeCells.length; i++) {
await this.jupyterApp.commands.execute(
'notebook:run-cell-and-select-next'
);
await waitForKernelStatus(this.widget.node, 'idle');
const expectedCount = this.options.expectedNumberOfVariables[i];
if (typeof expectedCount !== 'undefined') {
await page.waitForSelector(
`.jp-DebuggerVariables-body li:nth-child(${expectedCount + 2})`,
{
state: 'attached'
}
);
}
await layoutReady();
}
}

async cleanup(): Promise<void> {
if (!this.widget || !this.options) {
throw new Error('Setup failure');
}
// execute `%reset -f` cell
await layoutReady();
const minExpected = Math.min(...this.options.expectedNumberOfVariables);
await this.jupyterApp.commands.execute('notebook:run-cell-and-select-next');
// first two are "special variables" and "function variables"
await waitForKernelStatus(this.widget.node, 'idle');
await waitUntilDisappears(
`.jp-DebuggerVariables-body li:nth-child(${minExpected})`
);
await this._goToTop();
await layoutReady();
}
}

async function waitForKernelStatus(notebookPanel: HTMLElement, status: string) {
await new ElementHandle(notebookPanel).waitForSelector(
`.jp-Notebook-ExecutionIndicator[data-status="${status}"]`,
{ state: 'attached' }
);
}

async function activateTabWidget(
jupyterApp: JupyterFrontEnd,
widget: MainAreaWidget
Expand Down
30 changes: 30 additions & 0 deletions src/schema/scenario-debugger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"title": "Debugger Scenario Options",
"type": "object",
"properties": {
"codeCells": {
"title": "Code to execute",
"description": "Python code cells to execute one-by-one to populate the debugger namespace, e.g. `from numpy import *`",
"type": "array",
"items": {
"type": "string"
},
"default": [
"[globals().__setitem__(f'x{i}', 'y') for i in range(1000)];",
"z = 1"
]
},
"expectedNumberOfVariables": {
"title": "Expected number of variables",
"description": "The scenario waits until debugger panel is populated with at least as many variables as specified. For accurate timings should have as many members as there are code cells.",
"type": "array",
"items": {
"type": "integer",
"min": 0
},
"default": [1000, 1001]
}
},
"required": ["codeCells", "expectedNumberOfVariables"],
"additionalProperties": false
}
20 changes: 20 additions & 0 deletions src/types/_scenario-debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* tslint:disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/

/**
* Python code cells to execute one-by-one to populate the debugger namespace, e.g. `from numpy import *`
*/
export type CodeToExecute = string[];
/**
* The scenario waits until debugger panel is populated with at least as many variables as specified. For accurate timings should have as many members as there are code cells.
*/
export type ExpectedNumberOfVariables = number[];

export interface DebuggerScenarioOptions {
codeCells: CodeToExecute;
expectedNumberOfVariables: ExpectedNumberOfVariables;
}
Binary file modified ui-tests/tests/profiler.spec.ts-snapshots/ui-profiler-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.