Skip to content

Commit

Permalink
Add debugger variable list scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Jan 4, 2023
1 parent 73750e9 commit cbfd1f5
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 9 deletions.
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 new ElementHandle(this.widget.node).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;
}

0 comments on commit cbfd1f5

Please sign in to comment.