forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.ts
More file actions
146 lines (139 loc) · 6.18 KB
/
helpers.ts
File metadata and controls
146 lines (139 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as playwright from 'playwright-chromium';
import { IAsyncDisposable, IDisposable } from '../../../client/common/types';
import { createDeferred } from '../../../client/common/utils/async';
import { InteractiveWindowMessages } from '../../../client/datascience/interactive-common/interactiveWindowTypes';
import { CssMessages } from '../../../client/datascience/messages';
import { CommonActionType } from '../../../datascience-ui/interactive-common/redux/reducers/types';
import { IWebServer } from './webBrowserPanel';
// tslint:disable:max-func-body-length trailing-comma no-any no-multiline-string
export type WaitForMessageOptions = {
/**
* Timeout for waiting for message.
* Defaults to 65_000ms.
*
* @type {number}
*/
timeoutMs?: number;
/**
* Number of times the message should be received.
* Defaults to 1.
*
* @type {number}
*/
numberOfTimes?: number;
};
const maxWaitTimeForMessage = 75_000;
/**
* UI could take a while to update, could be slower on CI server.
* (500ms is generally enough, but increasing to 3s to avoid flaky CI tests).
*/
export const waitTimeForUIToUpdate = 10_000;
export class BaseWebUI implements IAsyncDisposable {
public page?: playwright.Page;
private readonly disposables: IDisposable[] = [];
private readonly webServerPromise = createDeferred<IWebServer>();
private webServer?: IWebServer;
private browser?: playwright.ChromiumBrowser;
public async dispose() {
while (this.disposables.length) {
this.disposables.shift()?.dispose(); // NOSONAR
}
await this.browser?.close();
await this.page?.close();
}
public async type(text: string): Promise<void> {
await this.page?.keyboard.type(text);
}
public _setWebServer(webServer: IWebServer) {
this.webServer = webServer;
this.webServerPromise.resolve(webServer);
}
public async waitUntilLoaded(): Promise<void> {
await this.webServerPromise.promise.then(() =>
// The UI is deemed loaded when we have seen all of the following messages.
// We cannot guarantee the order of these messages, however they are all part of the load process.
Promise.all([
this.waitForMessage(InteractiveWindowMessages.LoadAllCellsComplete),
this.waitForMessage(InteractiveWindowMessages.LoadAllCells),
this.waitForMessage(InteractiveWindowMessages.MonacoReady), // Sometimes the last thing to happen.
this.waitForMessage(InteractiveWindowMessages.SettingsUpdated),
this.waitForMessage(CommonActionType.EDITOR_LOADED),
this.waitForMessage(CommonActionType.CODE_CREATED), // When a cell has been created.
this.waitForMessage(CssMessages.GetMonacoThemeResponse),
this.waitForMessage(CssMessages.GetCssResponse)
])
);
}
public waitForMessageAfterServer(message: string, options?: WaitForMessageOptions): Promise<void> {
return this.webServerPromise.promise.then(() => this.waitForMessage(message, options));
}
public async waitForMessage(message: string, options?: WaitForMessageOptions): Promise<void> {
if (!this.webServer) {
throw new Error('WebServer not yet started');
}
const timeoutMs = options && options.timeoutMs ? options.timeoutMs : maxWaitTimeForMessage;
const numberOfTimes = options && options.numberOfTimes ? options.numberOfTimes : 1;
// Wait for the mounted web panel to send a message back to the data explorer
const promise = createDeferred<void>();
const timer = timeoutMs
? setTimeout(() => {
if (!promise.resolved) {
promise.reject(new Error(`Waiting for ${message} timed out`));
}
}, timeoutMs)
: undefined;
let timesMessageReceived = 0;
const dispatchedAction = `DISPATCHED_ACTION_${message}`;
const disposable = this.webServer.onDidReceiveMessage((msg) => {
const messageType = msg.type;
if (messageType !== message && messageType !== dispatchedAction) {
return;
}
timesMessageReceived += 1;
if (timesMessageReceived < numberOfTimes) {
return;
}
if (timer) {
clearTimeout(timer);
}
disposable.dispose();
if (messageType === message) {
promise.resolve();
} else {
// It could be a redux dispatched message.
// Wait for 10ms, wait for other stuff to finish.
// We can wait for 100ms or 1s. But thats too long.
// The assumption is that currently we do not have any setTimeouts
// in UI code that's in the magnitude of 100ms or more.
// We do have a couple of setTimeout's, but they wait for 1ms, not 100ms.
// 10ms more than sufficient for all the UI timeouts.
setTimeout(() => promise.resolve(), 10);
}
});
return promise.promise;
}
/**
* Opens a browser an loads the webpage, effectively loading the UI.
*/
public async loadUI(url: string) {
// Configure to display browser while debugging.
const openBrowser = process.env.VSC_PYTHON_DS_UI_BROWSER !== undefined;
this.browser = await playwright.chromium.launch({ headless: !openBrowser, devtools: openBrowser });
await this.browser.newContext();
this.page = await this.browser.newPage();
await this.page.goto(url);
}
public async captureScreenshot(filePath: string): Promise<void> {
if (!(await fs.pathExists(path.basename(filePath)))) {
await fs.ensureDir(path.basename(filePath));
}
await this.page?.screenshot({ path: filePath });
// tslint:disable-next-line: no-console
console.info(`Screenshot captured in ${filePath}`);
}
}