Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5

This commit is contained in:
ADS Merger
2020-02-08 04:50:58 +00:00
parent 8c61538a27
commit 2af13c18d2
752 changed files with 16458 additions and 10063 deletions

View File

@@ -133,6 +133,7 @@ export class Application {
extraArgs,
remote: this.options.remote,
web: this.options.web,
browser: this.options.browser,
headless: this.options.headless
});

View File

@@ -10,7 +10,7 @@ import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import { tmpName } from 'tmp';
import { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable } from './driver';
import { connect as connectPuppeteerDriver, launch } from './puppeteerDriver';
import { connect as connectPlaywrightDriver, launch } from './playwrightDriver';
import { Logger } from './logger';
import { ncp } from 'ncp';
import { URI } from 'vscode-uri';
@@ -101,6 +101,8 @@ export interface SpawnOptions {
remote?: boolean;
/** Run in the web */
web?: boolean;
/** A specific browser to use (requires web: true) */
browser?: 'chromium' | 'webkit' | 'firefox';
/** Run in headless mode (only applies when web is true) */
headless?: boolean;
}
@@ -120,68 +122,69 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath();
const handle = await createDriverHandle();
const args = [
options.workspacePath,
'--skip-getting-started',
'--skip-release-notes',
'--sticky-quickopen',
'--disable-telemetry',
'--disable-updates',
'--disable-crash-reporter',
`--extensions-dir=${options.extensionsPath}`,
`--user-data-dir=${options.userDataDir}`,
'--driver', handle
];
const env = process.env;
if (options.remote) {
// Replace workspace path with URI
args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`;
if (codePath) {
// running against a build: copy the test resolver extension
const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver');
if (!fs.existsSync(testResolverExtPath)) {
const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver');
await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c()));
}
}
args.push('--enable-proposed-api=vscode.vscode-test-resolver');
const remoteDataDir = `${options.userDataDir}-server`;
mkdirp.sync(remoteDataDir);
env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir;
}
if (!codePath) {
args.unshift(repoPath);
}
if (options.verbose) {
args.push('--driver-verbose');
}
if (options.log) {
args.push('--log', options.log);
}
if (options.extraArgs) {
args.push(...options.extraArgs);
}
let child: cp.ChildProcess | undefined;
let connectDriver: typeof connectElectronDriver;
if (options.web) {
await launch(args);
connectDriver = connectPuppeteerDriver.bind(connectPuppeteerDriver, !!options.headless);
await launch(options.userDataDir, options.workspacePath, options.codePath);
connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, !!options.headless, options.browser);
} else {
const env = process.env;
const args = [
options.workspacePath,
'--skip-getting-started',
'--skip-release-notes',
'--sticky-quickopen',
'--disable-telemetry',
'--disable-updates',
'--disable-crash-reporter',
`--extensions-dir=${options.extensionsPath}`,
`--user-data-dir=${options.userDataDir}`,
'--driver', handle
];
if (options.remote) {
// Replace workspace path with URI
args[0] = `--${options.workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(options.workspacePath).path}`;
if (codePath) {
// running against a build: copy the test resolver extension
const testResolverExtPath = path.join(options.extensionsPath, 'vscode-test-resolver');
if (!fs.existsSync(testResolverExtPath)) {
const orig = path.join(repoPath, 'extensions', 'vscode-test-resolver');
await new Promise((c, e) => ncp(orig, testResolverExtPath, err => err ? e(err) : c()));
}
}
args.push('--enable-proposed-api=vscode.vscode-test-resolver');
const remoteDataDir = `${options.userDataDir}-server`;
mkdirp.sync(remoteDataDir);
env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir;
}
if (!codePath) {
args.unshift(repoPath);
}
if (options.verbose) {
args.push('--driver-verbose');
}
if (options.log) {
args.push('--log', options.log);
}
if (options.extraArgs) {
args.push(...options.extraArgs);
}
const spawnOptions: cp.SpawnOptions = { env };
child = cp.spawn(electronPath, args, spawnOptions);
instances.add(child);
child.once('exit', () => instances.delete(child!));
connectDriver = connectElectronDriver;
}
return connect(connectDriver, child, outPath, handle, options.logger);
}

View File

@@ -89,7 +89,7 @@ export class Editor {
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
const textarea = `${editor} textarea`;
await this.code.waitAndClick(line, 0, 0);
await this.code.waitAndClick(line, 1, 1);
await this.code.waitForActiveElement(textarea);
}

View File

@@ -17,27 +17,27 @@ export class Editors {
}
}
async selectTab(tabName: string, untitled: boolean = false): Promise<void> {
await this.code.waitAndClick(`.tabs-container div.tab[aria-label="${tabName}, tab"]`);
await this.waitForEditorFocus(tabName, untitled);
async selectTab(fileName: string): Promise<void> {
await this.code.waitAndClick(`.tabs-container div.tab[data-resource-name$="${fileName}"]`);
await this.waitForEditorFocus(fileName);
}
async waitForActiveEditor(filename: string): Promise<any> {
const selector = `.editor-instance .monaco-editor[data-uri$="${filename}"] textarea`;
async waitForActiveEditor(fileName: string): Promise<any> {
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
return this.code.waitForActiveElement(selector);
}
async waitForEditorFocus(fileName: string, untitled: boolean = false): Promise<void> {
async waitForEditorFocus(fileName: string): Promise<void> {
await this.waitForActiveTab(fileName);
await this.waitForActiveEditor(fileName);
}
async waitForActiveTab(fileName: string, isDirty: boolean = false): Promise<void> {
await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][aria-label="${fileName}, tab"]`);
await this.code.waitForElement(`.tabs-container div.tab.active${isDirty ? '.dirty' : ''}[aria-selected="true"][data-resource-name$="${fileName}"]`);
}
async waitForTab(fileName: string, isDirty: boolean = false): Promise<void> {
await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[aria-label="${fileName}, tab"]`);
await this.code.waitForElement(`.tabs-container div.tab${isDirty ? '.dirty' : ''}[data-resource-name$="${fileName}"]`);
}
async newUntitledFile(): Promise<void> {
@@ -47,6 +47,6 @@ export class Editors {
await this.code.dispatchKeybinding('ctrl+n');
}
await this.waitForEditorFocus('Untitled-1', true);
await this.waitForEditorFocus('Untitled-1');
}
}

View File

@@ -3,17 +3,18 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as puppeteer from 'puppeteer';
import * as playwright from 'playwright';
import { ChildProcess, spawn } from 'child_process';
import { join } from 'path';
import { mkdir } from 'fs';
import { promisify } from 'util';
import { IDriver, IDisposable } from './driver';
import { URI } from 'vscode-uri';
const width = 1200;
const height = 800;
const vscodeToPuppeteerKey: { [key: string]: string } = {
const vscodeToPlaywrightKey: { [key: string]: string } = {
cmd: 'Meta',
ctrl: 'Control',
shift: 'Shift',
@@ -26,7 +27,7 @@ const vscodeToPuppeteerKey: { [key: string]: string } = {
home: 'Home'
};
function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver {
function buildDriver(browser: playwright.Browser, page: playwright.Page): IDriver {
const driver: IDriver = {
_serviceBrand: undefined,
getWindowIds: () => {
@@ -45,8 +46,8 @@ function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver
const keys = chord.split('+');
const keysDown: string[] = [];
for (let i = 0; i < keys.length; i++) {
if (keys[i] in vscodeToPuppeteerKey) {
keys[i] = vscodeToPuppeteerKey[keys[i]];
if (keys[i] in vscodeToPlaywrightKey) {
keys[i] = vscodeToPlaywrightKey[keys[i]];
}
await page.keyboard.down(keys[i]);
keysDown.push(keys[i]);
@@ -68,7 +69,7 @@ function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver
await driver.click(windowId, selector, 0, 0);
await timeout(100);
},
setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`),
setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`).then(undefined),
getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`),
isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`),
getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`),
@@ -86,25 +87,32 @@ function timeout(ms: number): Promise<void> {
// function runInDriver(call: string, args: (string | boolean)[]): Promise<any> {}
let args: string[] | undefined;
let server: ChildProcess | undefined;
let endpoint: string | undefined;
let workspacePath: string | undefined;
export async function launch(_args: string[]): Promise<void> {
args = _args;
const agentFolder = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', '');
export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH): Promise<void> {
workspacePath = _workspacePath;
const agentFolder = userDataDir;
await promisify(mkdir)(agentFolder);
const env = {
VSCODE_AGENT_FOLDER: agentFolder,
VSCODE_REMOTE_SERVER_PATH: codeServerPath,
...process.env
};
let serverLocation: string | undefined;
if (codeServerPath) {
serverLocation = join(codeServerPath, `server.${process.platform === 'win32' ? 'cmd' : 'sh'}`);
} else {
serverLocation = join(__dirname, '..', '..', '..', `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`);
}
server = spawn(
join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`),
serverLocation,
['--browser', 'none', '--driver', 'web'],
{ env }
);
server.stderr.on('data', e => console.log('Server stderr: ' + e));
server.stdout.on('data', e => console.log('Server stdout: ' + e));
server.stderr?.on('data', error => console.log(`Server stderr: ${error}`));
server.stdout?.on('data', data => console.log(`Server stdout: ${data}`));
process.on('exit', teardown);
process.on('SIGINT', teardown);
process.on('SIGTERM', teardown);
@@ -120,7 +128,7 @@ function teardown(): void {
function waitForEndpoint(): Promise<string> {
return new Promise<string>(r => {
server!.stdout.on('data', (d: Buffer) => {
server!.stdout?.on('data', (d: Buffer) => {
const matches = d.toString('ascii').match(/Web UI available at (.+)/);
if (matches !== null) {
r(matches[1]);
@@ -129,20 +137,18 @@ function waitForEndpoint(): Promise<string> {
});
}
export function connect(headless: boolean, outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }> {
export function connect(headless: boolean, engine: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> {
return new Promise(async (c) => {
const browser = await puppeteer.launch({
const browser = await playwright[engine].launch({
// Run in Edge dev on macOS
// executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev',
headless,
slowMo: 80,
args: [`--window-size=${width},${height}`]
headless
});
const page = (await browser.pages())[0];
const page = (await browser.defaultContext().pages())[0];
await page.setViewport({ width, height });
await page.goto(`${endpoint}&folder=${args![1]}`);
await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}`);
const result = {
client: { dispose: () => teardown },
client: { dispose: () => browser.close() && teardown() },
driver: buildDriver(browser, page)
};
c(result);

View File

@@ -12,7 +12,7 @@ export const enum ProblemSeverity {
export class Problems {
static PROBLEMS_VIEW_SELECTOR = '.panel.markers-panel';
static PROBLEMS_VIEW_SELECTOR = '.panel .markers-panel';
constructor(private code: Code) { }

View File

@@ -109,11 +109,12 @@ export class Search extends Viewlet {
}
async waitForResultText(text: string): Promise<void> {
await this.code.waitForTextContent(`${VIEWLET} .messages .message>p`, text);
// The label can end with " - " depending on whether the search editor is enabled
await this.code.waitForTextContent(`${VIEWLET} .messages .message>span`, undefined, result => result.startsWith(text));
}
async waitForNoResultText(): Promise<void> {
await this.code.waitForElement(`${VIEWLET} .messages[aria-hidden="true"] .message>p`);
await this.code.waitForElement(`${VIEWLET} .messages[aria-hidden="true"] .message>span`);
}
private async waitForInputFocus(selector: string): Promise<void> {

View File

@@ -20,8 +20,6 @@ export const enum StatusBarElement {
export class StatusBar {
private readonly mainSelector = 'div[id="workbench.parts.statusbar"]';
private readonly leftSelector = '.statusbar-item.left';
private readonly rightSelector = '.statusbar-item.right';
constructor(private code: Code) { }
@@ -44,23 +42,23 @@ export class StatusBar {
private getSelector(element: StatusBarElement): string {
switch (element) {
case StatusBarElement.BRANCH_STATUS:
return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-git-branch`;
return `.statusbar-item[id="status.scm"] .codicon.codicon-git-branch`;
case StatusBarElement.SYNC_STATUS:
return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-sync`;
return `.statusbar-item[id="status.scm"] .codicon.codicon-sync`;
case StatusBarElement.PROBLEMS_STATUS:
return `${this.mainSelector} ${this.leftSelector} .codicon.codicon-error`;
return `.statusbar-item[id="status.problems"]`;
case StatusBarElement.SELECTION_STATUS:
return `${this.mainSelector} ${this.rightSelector}[title="Go to Line"]`;
return `.statusbar-item[id="status.editor.selection"]`;
case StatusBarElement.INDENTATION_STATUS:
return `${this.mainSelector} ${this.rightSelector}[title="Select Indentation"]`;
return `.statusbar-item[id="status.editor.indentation"]`;
case StatusBarElement.ENCODING_STATUS:
return `${this.mainSelector} ${this.rightSelector}[title="Select Encoding"]`;
return `.statusbar-item[id="status.editor.encoding"]`;
case StatusBarElement.EOL_STATUS:
return `${this.mainSelector} ${this.rightSelector}[title="Select End of Line Sequence"]`;
return `.statusbar-item[id="status.editor.eol"]`;
case StatusBarElement.LANGUAGE_STATUS:
return `${this.mainSelector} ${this.rightSelector}[title="Select Language Mode"]`;
return `.statusbar-item[id="status.editor.mode"]`;
case StatusBarElement.FEEDBACK_ICON:
return `${this.mainSelector} .statusbar-item.right[id="status.feedback"]`;
return `.statusbar-item[id="status.feedback"]`;
default:
throw new Error(element);
}