VS Code merge to df8fe74bd55313de0dd2303bc47a4aab0ca56b0e (#17979)

* Merge from vscode 504f934659740e9d41501cad9f162b54d7745ad9

* delete unused folders

* distro

* Bump build node version

* update chokidar

* FIx hygiene errors

* distro

* Fix extension lint issues

* Remove strict-vscode

* Add copyright header exemptions

* Bump vscode-extension-telemetry to fix webpacking issue with zone.js

* distro

* Fix failing tests (revert marked.js back to current one until we decide to update)

* Skip searchmodel test

* Fix mac build

* temp debug script loading

* Try disabling coverage

* log error too

* Revert "log error too"

This reverts commit af0183e5d4ab458fdf44b88fbfab9908d090526f.

* Revert "temp debug script loading"

This reverts commit 3d687d541c76db2c5b55626c78ae448d3c25089c.

* Add comments explaining coverage disabling

* Fix ansi_up loading issue

* Merge latest from ads

* Use newer option

* Fix compile

* add debug logging warn

* Always log stack

* log more

* undo debug

* Update to use correct base path (+cleanup)

* distro

* fix compile errors

* Remove strict-vscode

* Fix sql editors not showing

* Show db dropdown input & fix styling

* Fix more info in gallery

* Fix gallery asset requests

* Delete unused workflow

* Fix tapable resolutions for smoke test compile error

* Fix smoke compile

* Disable crash reporting

* Disable interactive

Co-authored-by: ADS Merger <karlb@microsoft.com>
This commit is contained in:
Charles Gagnon
2022-01-06 09:06:56 -08:00
committed by GitHub
parent fd2736b6a6
commit 2bc6a0cd01
2099 changed files with 79520 additions and 43813 deletions

View File

@@ -31,7 +31,7 @@
"npm-run-all": "^4.1.5",
"tmp": "0.1.0",
"tree-kill": "1.2.2",
"typescript": "3.7.5",
"typescript": "^4.3.2",
"vscode-uri": "^2.0.3",
"watch": "^1.0.2"
}

View File

@@ -28,6 +28,7 @@ export class Application {
private _workbench: Workbench | undefined;
constructor(private options: ApplicationOptions) {
this._userDataPath = options.userDataDir;
this._workspacePathOrFolder = options.workspacePath;
}
@@ -64,18 +65,14 @@ export class Application {
return this.options.extensionsPath;
}
private _userDataPath: string;
get userDataPath(): string {
return this.options.userDataDir;
return this._userDataPath;
}
async start(expectWalkthroughPart = true): Promise<any> {
async start(): Promise<any> {
await this._start();
await this.code.waitForElement('.object-explorer-view'); // {{SQL CARBON EDIT}} We have a different startup view
// https://github.com/microsoft/vscode/issues/118748
// if (expectWalkthroughPart) {
// await this.code.waitForElement(`.editor-instance > div > div.welcomePageFocusElement[tabIndex="0"]`);
// }
}
async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise<any> {
@@ -121,17 +118,8 @@ export class Application {
private async startApplication(extraArgs: string[] = []): Promise<any> {
this._code = await spawn({
codePath: this.options.codePath,
workspacePath: this.workspacePathOrFolder,
userDataDir: this.options.userDataDir,
extensionsPath: this.options.extensionsPath,
logger: this.options.logger,
verbose: this.options.verbose,
log: this.options.log,
extraArgs,
remote: this.options.remote,
web: this.options.web,
browser: this.options.browser
...this.options,
extraArgs: [...(this.options.extraArgs || []), ...extraArgs],
});
this._workbench = new Workbench(this._code, this.userDataPath);

View File

@@ -9,7 +9,7 @@ import * as os from 'os';
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 { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable, ILocalizedStrings, ILocaleInfo } from './driver';
import { connect as connectPlaywrightDriver, launch } from './playwrightDriver';
import { Logger } from './logger';
import { ncp } from 'ncp';
@@ -97,11 +97,9 @@ export interface SpawnOptions {
verbose?: boolean;
extraArgs?: string[];
log?: string;
/** Run in the test resolver */
remote?: boolean;
/** Run in the web */
web?: boolean;
/** A specific browser to use (requires web: true) */
headless?: boolean;
browser?: 'chromium' | 'webkit' | 'firefox';
}
@@ -123,18 +121,19 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
copyExtension(options.extensionsPath, 'vscode-notebook-tests');
if (options.web) {
await launch(options.userDataDir, options.workspacePath, options.codePath, options.extensionsPath);
connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options.browser);
await launch(options.userDataDir, options.workspacePath, options.codePath, options.extensionsPath, Boolean(options.verbose));
connectDriver = connectPlaywrightDriver.bind(connectPlaywrightDriver, options);
return connect(connectDriver, child, '', handle, options.logger);
}
const env = process.env;
const env = { ...process.env };
const codePath = options.codePath;
const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath();
const args = [
options.workspacePath,
'--skip-release-notes',
'--skip-welcome',
'--disable-telemetry',
'--no-cached-data',
'--disable-updates',
@@ -143,6 +142,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
'--disable-workspace-trust',
`--extensions-dir=${options.extensionsPath}`,
`--user-data-dir=${options.userDataDir}`,
`--logsPath=${path.join(repoPath, '.build', 'logs', 'smoke-tests')}`,
'--driver', handle
];
@@ -172,6 +172,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir;
}
const spawnOptions: cp.SpawnOptions = { env };
args.push('--enable-proposed-api=vscode.vscode-notebook-tests');
@@ -181,6 +182,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
if (options.verbose) {
args.push('--driver-verbose');
spawnOptions.stdio = ['ignore', 'inherit', 'inherit'];
}
if (options.log) {
@@ -192,7 +194,6 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
}
const electronPath = codePath ? getBuildElectronPath(codePath) : getDevElectronPath();
const spawnOptions: cp.SpawnOptions = { env };
child = cp.spawn(electronPath, args, spawnOptions);
instances.add(child);
child.once('exit', () => instances.delete(child!));
@@ -235,7 +236,7 @@ async function poll<T>(
} else {
lastError = 'Did not pass accept function';
}
} catch (e) {
} catch (e: any) {
lastError = Array.isArray(e.stack) ? e.stack.join(os.EOL) : e.stack;
}
@@ -293,7 +294,10 @@ export class Code {
}
async exit(): Promise<void> {
await this.driver.exitApplication();
const veto = await this.driver.exitApplication();
if (veto === true) {
throw new Error('Code exit was blocked by a veto.');
}
}
async waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean, retryCount?: number): Promise<string> {
@@ -372,6 +376,16 @@ export class Code {
await poll(() => this.driver.writeInTerminal(windowId, selector, value), () => true, `writeInTerminal '${selector}'`);
}
async getLocaleInfo(): Promise<ILocaleInfo> {
const windowId = await this.getActiveWindowId();
return await this.driver.getLocaleInfo(windowId);
}
async getLocalizedStrings(): Promise<ILocalizedStrings> {
const windowId = await this.getActiveWindowId();
return await this.driver.getLocalizedStrings(windowId);
}
private async getActiveWindowId(): Promise<number> {
if (typeof this._activeWindowId !== 'number') {
const windows = await this.driver.getWindowIds();

View File

@@ -23,6 +23,7 @@ export * from './settings';
export * from './statusbar';
export * from './terminal';
export * from './viewlet';
export * from './localization';
export * from './workbench';
export * from './driver';

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Code } from './code';
import { ILocalizedStrings, ILocaleInfo } from './driver';
export class Localization {
constructor(private code: Code) { }
async getLocaleInfo(): Promise<ILocaleInfo> {
return this.code.getLocaleInfo();
}
async getLocalizedStrings(): Promise<ILocalizedStrings> {
return this.code.getLocalizedStrings();
}
}

View File

@@ -15,6 +15,9 @@ import * as kill from 'tree-kill';
const width = 1200;
const height = 800;
const root = join(__dirname, '..', '..', '..');
const logsPath = join(root, '.build', 'logs', 'smoke-tests-browser');
const vscodeToPlaywrightKey: { [key: string]: string } = {
cmd: 'Meta',
ctrl: 'Control',
@@ -29,7 +32,9 @@ const vscodeToPlaywrightKey: { [key: string]: string } = {
esc: 'Escape'
};
function buildDriver(browser: playwright.Browser, page: playwright.Page): IDriver {
let traceCounter = 1;
function buildDriver(browser: playwright.Browser, context: playwright.BrowserContext, page: playwright.Page): IDriver {
const driver: IDriver = {
_serviceBrand: undefined,
getWindowIds: () => {
@@ -41,7 +46,17 @@ function buildDriver(browser: playwright.Browser, page: playwright.Page): IDrive
return buffer.toString('base64');
},
reloadWindow: (windowId) => Promise.resolve(),
exitApplication: () => browser.close(),
exitApplication: async () => {
try {
await context.tracing.stop({ path: join(logsPath, `playwright-trace-${traceCounter++}.zip`) });
} catch (error) {
console.warn(`Failed to stop playwright tracing.`); // do not fail the build when this fails
}
await browser.close();
await teardown();
return false;
},
dispatchKeybinding: async (windowId, keybinding) => {
const chords = keybinding.split(' ');
for (let i = 0; i < chords.length; i++) {
@@ -82,7 +97,9 @@ function buildDriver(browser: playwright.Browser, page: playwright.Page): IDrive
getElementXY: (windowId, selector, xoffset?, yoffset?) => page.evaluate(`window.driver.getElementXY('${selector}', ${xoffset}, ${yoffset})`),
typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`),
getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`),
writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`)
writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`),
getLocaleInfo: (windowId) => page.evaluate(`window.driver.getLocaleInfo()`),
getLocalizedStrings: (windowId) => page.evaluate(`window.driver.getLocalizedStrings()`)
};
return driver;
}
@@ -91,11 +108,12 @@ function timeout(ms: number): Promise<void> {
return new Promise<void>(r => setTimeout(r, ms));
}
let port = 9000;
let server: ChildProcess | undefined;
let endpoint: string | undefined;
let workspacePath: string | undefined;
export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, extPath: string): Promise<void> {
export async function launch(userDataDir: string, _workspacePath: string, codeServerPath = process.env.VSCODE_REMOTE_SERVER_PATH, extPath: string, verbose: boolean): Promise<void> {
workspacePath = _workspacePath;
const agentFolder = userDataDir;
@@ -105,31 +123,54 @@ export async function launch(userDataDir: string, _workspacePath: string, codeSe
VSCODE_REMOTE_SERVER_PATH: codeServerPath,
...process.env
};
const args = ['--browser', 'none', '--driver', 'web', '--extensions-dir', extPath];
const args = ['--disable-telemetry', '--port', `${port++}`, '--browser', 'none', '--driver', 'web', '--extensions-dir', extPath];
let serverLocation: string | undefined;
if (codeServerPath) {
serverLocation = join(codeServerPath, `server.${process.platform === 'win32' ? 'cmd' : 'sh'}`);
console.log(`Starting built server from '${serverLocation}'`);
args.push(`--logsPath=${logsPath}`);
if (verbose) {
console.log(`Starting built server from '${serverLocation}'`);
console.log(`Storing log files into '${logsPath}'`);
}
} else {
serverLocation = join(__dirname, '..', '..', '..', `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`);
console.log(`Starting server out of sources from '${serverLocation}'`);
serverLocation = join(root, `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`);
args.push('--logsPath', logsPath);
if (verbose) {
console.log(`Starting server out of sources from '${serverLocation}'`);
console.log(`Storing log files into '${logsPath}'`);
}
}
server = spawn(
serverLocation,
args,
{ env }
);
server.stderr?.on('data', error => console.log(`Server stderr: ${error}`));
server.stdout?.on('data', data => console.log(`Server stdout: ${data}`));
if (verbose) {
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);
endpoint = await waitForEndpoint();
}
function teardown(): void {
async function teardown(): Promise<void> {
if (server) {
kill(server.pid);
try {
await new Promise<void>((c, e) => kill(server!.pid, err => err ? e(err) : c()));
} catch {
// noop
}
server = undefined;
}
}
@@ -145,17 +186,39 @@ function waitForEndpoint(): Promise<string> {
});
}
export function connect(browserType: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> {
interface Options {
readonly browser?: 'chromium' | 'webkit' | 'firefox';
readonly headless?: boolean;
}
export function connect(options: Options = {}): Promise<{ client: IDisposable, driver: IDriver }> {
return new Promise(async (c) => {
const browser = await playwright[browserType].launch({ headless: false });
const browser = await playwright[options.browser ?? 'chromium'].launch({ headless: options.headless ?? false });
const context = await browser.newContext({ permissions: ['clipboard-read'] }); // {{SQL CARBON EDIT}} avoid permissison request
try {
await context.tracing.start({ screenshots: true, snapshots: true });
} catch (error) {
console.warn(`Failed to start playwright tracing.`); // do not fail the build when this fails
}
const page = await context.newPage();
await page.setViewportSize({ width, height });
const payloadParam = `[["enableProposedApi",""]]`;
page.on('pageerror', async error => console.error(`Playwright ERROR: page error: ${error}`));
page.on('crash', page => console.error('Playwright ERROR: page crash'));
page.on('response', async response => {
if (response.status() >= 400) {
console.error(`Playwright ERROR: HTTP status ${response.status()} for ${response.url()}`);
}
});
const payloadParam = `[["enableProposedApi",""],["skipWelcome","true"]]`;
await page.goto(`${endpoint}&folder=vscode-remote://localhost:9888${URI.file(workspacePath!).path}&payload=${payloadParam}`);
const result = {
client: { dispose: () => browser.close() && teardown() },
driver: buildDriver(browser, page)
client: {
dispose: () => {
browser.close();
teardown();
}
},
driver: buildDriver(browser, context, page)
};
c(result);
});

View File

@@ -20,6 +20,7 @@ import { Editors } from './editors';
import { Code } from './code';
import { Terminal } from './terminal';
import { Notebook } from './notebook';
import { Localization } from './localization';
// {{SQL CARBON EDIT}}
import { ConnectionDialog } from './sql/connectionDialog';
@@ -55,6 +56,7 @@ export class Workbench {
readonly keybindingsEditor: KeybindingsEditor;
readonly terminal: Terminal;
readonly notebook: Notebook;
readonly localization: Localization;
// {{SQL CARBON EDIT}}
readonly connectionDialog: ConnectionDialog;
@@ -96,5 +98,6 @@ export class Workbench {
this.addRemoteBookDialog = new AddRemoteBookDialog(code);
// {{END}}
this.notebook = new Notebook(this.quickaccess, code);
this.localization = new Localization(code);
}
}

View File

@@ -659,10 +659,10 @@ tree-kill@1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
typescript@3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
typescript@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
universalify@^2.0.0:
version "2.0.0"

View File

@@ -2,9 +2,10 @@
## Compile
Make sure to run the following command to compile and install dependencies:
Make sure to run the following commands to compile and install dependencies:
yarn --cwd test/integration/browser
yarn --cwd test/integration/browser compile
## Run (inside Electron)

View File

@@ -12,9 +12,10 @@ import * as rimraf from 'rimraf';
import { URI } from 'vscode-uri';
import * as kill from 'tree-kill';
import * as optimistLib from 'optimist';
import { StdioOptions } from 'node:child_process';
const optimist = optimistLib
.describe('workspacePath', 'path to the workspace to open in the test').string('workspacePath')
.describe('workspacePath', 'path to the workspace (folder or *.code-workspace file) to open in the test').string('workspacePath')
.describe('extensionDevelopmentPath', 'path to the extension to test').string('extensionDevelopmentPath')
.describe('extensionTestsPath', 'path to the extension tests').string('extensionTestsPath')
.describe('debug', 'do not run browsers headless').boolean('debug')
@@ -32,12 +33,19 @@ const height = 800;
type BrowserType = 'chromium' | 'firefox' | 'webkit';
async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise<void> {
const args = process.platform === 'linux' && browserType === 'chromium' ? ['--disable-setuid-sandbox'] : undefined; // setuid sandboxes requires root and is used in containers so we disable this to support our CI
const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug), args });
const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug) });
const context = await browser.newContext();
const page = await context.newPage();
await page.setViewportSize({ width, height });
page.on('pageerror', async error => console.error(`Playwright ERROR: page error: ${error}`));
page.on('crash', page => console.error('Playwright ERROR: page crash'));
page.on('response', async response => {
if (response.status() >= 400) {
console.error(`Playwright ERROR: HTTP status ${response.status()} for ${response.url()}`);
}
});
const host = endpoint.host;
const protocol = 'vscode-remote';
@@ -45,32 +53,32 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith
const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true });
const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true });
const folderParam = testWorkspaceUri;
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","5319757634f77a050b49c10162939bfe60970c29"]]`;
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","5319757634f77a050b49c10162939bfe60970c29"],["skipWelcome","true"]]`;
await page.goto(`${endpoint.href}&folder=${folderParam}&payload=${payloadParam}`);
if (path.extname(testWorkspaceUri) === '.code-workspace') {
await page.goto(`${endpoint.href}&workspace=${testWorkspaceUri}&payload=${payloadParam}`);
} else {
await page.goto(`${endpoint.href}&folder=${testWorkspaceUri}&payload=${payloadParam}`);
}
await page.exposeFunction('codeAutomationLog', (type: string, args: any[]) => {
console[type](...args);
});
page.on('console', async (msg: playwright.ConsoleMessage) => {
const msgText = msg.text();
if (msgText.indexOf('vscode:exit') >= 0) {
try {
await browser.close();
} catch (error) {
console.error(`Error when closing browser: ${error}`);
}
try {
await pkill(server.pid);
} catch (error) {
console.error(`Error when killing server process tree: ${error}`);
}
process.exit(msgText === 'vscode:exit 0' ? 0 : 1);
await page.exposeFunction('codeAutomationExit', async (code: number) => {
try {
await browser.close();
} catch (error) {
console.error(`Error when closing browser: ${error}`);
}
try {
await pkill(server.pid);
} catch (error) {
console.error(`Error when killing server process tree: ${error}`);
}
process.exit(code);
});
}
@@ -95,28 +103,42 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U
...process.env
};
const root = path.join(__dirname, '..', '..', '..', '..');
const logsPath = path.join(root, '.build', 'logs', 'integration-tests-browser');
const serverArgs = ['--browser', 'none', '--driver', 'web', '--enable-proposed-api', '--disable-telemetry'];
let serverLocation: string;
if (process.env.VSCODE_REMOTE_SERVER_PATH) {
serverLocation = path.join(process.env.VSCODE_REMOTE_SERVER_PATH, `server.${process.platform === 'win32' ? 'cmd' : 'sh'}`);
serverArgs.push(`--logsPath=${logsPath}`);
console.log(`Starting built server from '${serverLocation}'`);
if (optimist.argv.debug) {
console.log(`Starting built server from '${serverLocation}'`);
console.log(`Storing log files into '${logsPath}'`);
}
} else {
serverLocation = path.join(__dirname, '..', '..', '..', '..', `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`);
serverLocation = path.join(root, `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`);
serverArgs.push('--logsPath', logsPath);
process.env.VSCODE_DEV = '1';
console.log(`Starting server out of sources from '${serverLocation}'`);
if (optimist.argv.debug) {
console.log(`Starting server out of sources from '${serverLocation}'`);
console.log(`Storing log files into '${logsPath}'`);
}
}
const stdio: StdioOptions = optimist.argv.debug ? 'pipe' : ['ignore', 'pipe', 'ignore'];
let serverProcess = cp.spawn(
serverLocation,
['--browser', 'none', '--driver', 'web', '--enable-proposed-api'],
{ env }
serverArgs,
{ env, stdio }
);
serverProcess?.stderr?.on('data', error => console.log(`Server stderr: ${error}`));
if (optimist.argv.debug) {
serverProcess?.stdout?.on('data', data => console.log(`Server stdout: ${data}`));
serverProcess.stderr!.on('data', error => console.log(`Server stderr: ${error}`));
serverProcess.stdout!.on('data', data => console.log(`Server stdout: ${data}`));
}
process.on('exit', () => serverProcess.kill());
@@ -124,7 +146,7 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U
process.on('SIGTERM', () => serverProcess.kill());
return new Promise(c => {
serverProcess?.stdout?.on('data', data => {
serverProcess.stdout!.on('data', data => {
const matches = data.toString('ascii').match(/Web UI available at (.+)/);
if (matches !== null) {
c({ endpoint: url.parse(matches[1]), server: serverProcess });

View File

@@ -6,4 +6,4 @@ out/
keybindings.*.json
test_data/
src/vscode/driver.d.ts
vscode-server*/
vscode-server*/

View File

@@ -21,8 +21,8 @@ yarn smoketest
yarn smoketest --web --browser [chromium|webkit]
# Build (Electron)
yarn smoketest --build <path to latest version> --stable-build <path to stable version>
example: yarn smoketest --build /Applications/Visual\ Studio\ Code\ -\ Insiders.app --stable-build /Applications/Visual\ Studio\ Code.app/
yarn smoketest --build <path to latest version>
example: yarn smoketest --build /Applications/Visual\ Studio\ Code\ -\ Insiders.app
# Build (Web - read instructions below)
yarn smoketest --build <path to server web build (ends in -web)> --web --browser [chromium|webkit]
@@ -44,17 +44,6 @@ yarn && yarn compile
yarn --cwd test/smoke
```
#### Electron with --build and --stable-build
In addition to the vscode repository, you will need the latest build and the previous stable build, so that the smoketest can test data migration.
The recommended way to make these builds available for the smoketest is by downloading their archive versions (\*.zip) from the **[builds page](https://builds.code.visualstudio.com/)**, and extracting
them into two folders (e.g. with 'Extract All' on Windows). Pass the **absolute paths** of those folders to the smoketest as follows:
```bash
yarn smoketest --build <path to latest version> --stable-build <path to stable version>
```
#### Web
There is no support for testing an old version to a new one yet.
@@ -81,6 +70,12 @@ cd test/smoke
yarn watch
```
## Troubleshooting
### Error: Could not get a unique tmp filename, max tries reached
On Windows, check for the folder `C:\Users\<username>\AppData\Local\Temp\t`. If this folder exists, the `tmp` module can't run properly, resulting in the error above. In this case, delete the `t` folder.
## Pitfalls
- Beware of workbench **state**. The tests within a single suite will share the same state.

View File

@@ -16,18 +16,20 @@
"@types/mocha": "^8.2.0",
"@types/ncp": "2.0.1",
"@types/node": "14.x",
"@types/node-fetch": "^2.5.10",
"@types/rimraf": "^2.0.4",
"@types/tmp": "0.0.33",
"cpx": "^1.5.0",
"htmlparser2": "^3.9.2",
"mkdirp": "^1.0.4",
"ncp": "^2.0.0",
"node-fetch": "^2.6.1",
"npm-run-all": "^4.1.5",
"portastic": "^1.0.1",
"rimraf": "^2.6.1",
"strip-json-comments": "^2.0.1",
"tmp": "0.0.33",
"typescript": "3.7.5",
"typescript": "^4.3.2",
"vscode-test": "^1.6.1",
"watch": "^1.0.2"
}
}

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Editor', () => {
beforeSuite(opts);
afterSuite(opts);
it('shows correct quick outline', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('www');
@@ -14,21 +19,5 @@ export function setup() {
await app.workbench.quickaccess.openQuickOutline();
await app.workbench.quickinput.waitForQuickInputElements(names => names.length >= 6);
});
// it('folds/unfolds the code correctly', async function () {
// await app.workbench.quickaccess.openFile('www');
// // Fold
// await app.workbench.editor.foldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilHidden(4);
// await app.workbench.editor.waitUntilHidden(5);
// // Unfold
// await app.workbench.editor.unfoldAtLine(3);
// await app.workbench.editor.waitUntilShown(3);
// await app.workbench.editor.waitUntilShown(4);
// await app.workbench.editor.waitUntilShown(5);
// });
});
}

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application, Quality } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Extensions', () => {
beforeSuite(opts);
afterSuite(opts);
it(`install and enable vscode-smoketest-check extension`, async function () {
const app = this.app as Application;
@@ -16,7 +21,7 @@ export function setup() {
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('michelkaporin.vscode-smoketest-check', true);
await app.workbench.extensions.installExtension('ms-vscode.vscode-smoketest-check', true);
// Close extension editor because keybindings dispatch is not working when web views are opened and focused
// https://github.com/microsoft/vscode/issues/110276

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application, ProblemSeverity, Problems } from '../../../../automation/out';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Language Features', () => {
beforeSuite(opts);
afterSuite(opts);
it('verifies quick outline', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openFile('style.css');

View File

@@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import minimist = require('minimist');
import * as path from 'path';
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
function toUri(path: string): string {
if (process.platform === 'win32') {
@@ -34,19 +36,15 @@ async function createWorkspaceFile(workspacePath: string): Promise<string> {
return workspaceFilePath;
}
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Multiroot', () => {
before(async function () {
const app = this.app as Application;
const workspaceFilePath = await createWorkspaceFile(app.workspacePathOrFolder);
// restart with preventing additional windows from restoring
// to ensure the window after restart is the multi-root workspace
await app.restart({ workspaceOrFolder: workspaceFilePath });
beforeSuite(opts, async opts => {
const workspacePath = await createWorkspaceFile(opts.workspacePath);
return { ...opts, workspacePath };
});
afterSuite(opts);
it('shows results from all folders', async function () {
const app = this.app as Application;
await app.workbench.quickaccess.openQuickAccess('*.*');

View File

@@ -4,20 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import minimist = require('minimist');
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
// function wait(ms: number): Promise<void> {
// return new Promise(r => setTimeout(r, ms));
// }
export function setup() {
describe('Notebooks', () => {
after(async function () {
const app = this.app as Application;
cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder });
cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder });
});
export function setup(opts: minimist.ParsedArgs) {
describe.skip('Notebooks', () => {
beforeSuite(opts);
afterEach(async function () {
const app = this.app as Application;
@@ -25,34 +18,42 @@ export function setup() {
await app.workbench.quickaccess.runCommand('workbench.action.closeActiveEditor');
});
// it('inserts/edits code cell', async function () {
// const app = this.app as Application;
// await app.workbench.notebook.openNotebook();
// await app.workbench.notebook.focusNextCell();
// await app.workbench.notebook.insertNotebookCell('code');
// await app.workbench.notebook.waitForTypeInEditor('// some code');
// await app.workbench.notebook.stopEditingCell();
// });
after(async function () {
const app = this.app as Application;
cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder });
cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder });
});
// it('inserts/edits markdown cell', async function () {
// const app = this.app as Application;
// await app.workbench.notebook.openNotebook();
// await app.workbench.notebook.focusNextCell();
// await app.workbench.notebook.insertNotebookCell('markdown');
// await app.workbench.notebook.waitForTypeInEditor('## hello2! ');
// await app.workbench.notebook.stopEditingCell();
// await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!');
// });
afterSuite(opts);
// it('moves focus as it inserts/deletes a cell', async function () {
// const app = this.app as Application;
// await app.workbench.notebook.openNotebook();
// await app.workbench.notebook.insertNotebookCell('code');
// await app.workbench.notebook.waitForActiveCellEditorContents('');
// await app.workbench.notebook.stopEditingCell();
// await app.workbench.notebook.deleteActiveCell();
// await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell');
// });
it.skip('inserts/edits code cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.focusNextCell();
await app.workbench.notebook.insertNotebookCell('code');
await app.workbench.notebook.waitForTypeInEditor('// some code');
await app.workbench.notebook.stopEditingCell();
});
it.skip('inserts/edits markdown cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.focusNextCell();
await app.workbench.notebook.insertNotebookCell('markdown');
await app.workbench.notebook.waitForTypeInEditor('## hello2! ');
await app.workbench.notebook.stopEditingCell();
await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!');
});
it.skip('moves focus as it inserts/deletes a cell', async function () {
const app = this.app as Application;
await app.workbench.notebook.openNotebook();
await app.workbench.notebook.insertNotebookCell('code');
await app.workbench.notebook.waitForActiveCellEditorContents('');
await app.workbench.notebook.stopEditingCell();
await app.workbench.notebook.deleteActiveCell();
await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell');
});
it.skip('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/113882
const app = this.app as Application;

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application, ActivityBarPosition } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Preferences', () => {
beforeSuite(opts);
afterSuite(opts);
it('turns off editor line numbers and verifies the live change', async function () {
const app = this.app as Application;
@@ -27,13 +32,5 @@ export function setup() {
await app.code.dispatchKeybinding('ctrl+u');
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.RIGHT);
});
after(async function () {
const app = this.app as Application;
await app.workbench.settingsEditor.clearUserSettings();
// Wait for settings to be applied, which will happen after the settings file is empty
await app.workbench.activitybar.waitForActivityBar(ActivityBarPosition.LEFT);
});
});
}

View File

@@ -4,17 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import * as cp from 'child_process';
import minimist = require('minimist');
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
// https://github.com/microsoft/vscode/issues/115244
describe('Search', () => {
beforeSuite(opts);
after(function () {
const app = this.app as Application;
cp.execSync('git checkout . --quiet', { cwd: app.workspacePathOrFolder });
cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder });
});
afterSuite(opts);
// https://github.com/microsoft/vscode/issues/124146
it.skip /* https://github.com/microsoft/vscode/issues/124335 */('has a tooltp with a keybinding', async function () {
const app = this.app as Application;
@@ -68,6 +74,9 @@ export function setup() {
});
describe('Quick Access', () => {
beforeSuite(opts);
afterSuite(opts);
it('quick access search produces correct result', async function () {
const app = this.app as Application;
const expectedNames = [

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application, Quality, StatusBarElement } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup(isWeb) {
export function setup(opts: minimist.ParsedArgs) {
describe('Statusbar', () => {
beforeSuite(opts);
afterSuite(opts);
it('verifies presence of all default status bar elements', async function () {
const app = this.app as Application;
@@ -18,7 +23,7 @@ export function setup(isWeb) {
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.PROBLEMS_STATUS);
await app.workbench.quickaccess.openFile('app.js');
if (!isWeb) {
if (!opts.web) {
// Encoding picker currently hidden in web (only UTF-8 supported)
await app.workbench.statusbar.waitForStatusbarElement(StatusBarElement.ENCODING_STATUS);
}
@@ -39,7 +44,7 @@ export function setup(isWeb) {
await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
if (!isWeb) {
if (!opts.web) {
// Encoding picker currently hidden in web (only UTF-8 supported)
await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS);
await app.workbench.quickinput.waitForQuickInputOpened();
@@ -60,17 +65,6 @@ export function setup(isWeb) {
await app.workbench.problems.waitForProblemsView();
});
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
return this.skip();
}
await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON);
await app.code.waitForElement('.feedback-form');
});
it(`checks if 'Go to Line' works if called from the status bar`, async function () {
const app = this.app as Application;
@@ -94,5 +88,16 @@ export function setup(isWeb) {
await app.workbench.statusbar.waitForEOL('CRLF');
});
it(`verifies that 'Tweet us feedback' pop-up appears when clicking on 'Feedback' icon`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
return this.skip();
}
await app.workbench.statusbar.clickOn(StatusBarElement.FEEDBACK_ICON);
await app.code.waitForElement('.feedback-form');
});
});
}

View File

@@ -3,10 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup(opts: minimist.ParsedArgs) {
export function setup() {
describe('Dataloss', () => {
beforeSuite(opts);
afterSuite(opts);
it(`verifies that 'hot exit' works for dirty files`, async function () {
const app = this.app as Application;
await app.workbench.editors.newUntitledFile();

View File

@@ -5,15 +5,25 @@
import { Application, ApplicationOptions, Quality } from '../../../../automation';
import { join } from 'path';
import { ParsedArgs } from 'minimist';
import { timeout } from '../../utils';
export function setup(stableCodePath: string, testDataPath: string) {
export function setup(opts: ParsedArgs, testDataPath: string) {
describe('Datamigration', () => {
it(`verifies opened editors are restored`, async function () {
const stableCodePath = opts['stable-build'];
if (!stableCodePath) {
this.skip();
}
// On macOS, the stable app fails to launch on first try,
// so let's retry this once
// https://github.com/microsoft/vscode/pull/127799
if (process.platform === 'darwin') {
this.retries(2);
}
const userDataDir = join(testDataPath, 'd2'); // different data dir from the other tests
const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
@@ -22,7 +32,7 @@ export function setup(stableCodePath: string, testDataPath: string) {
stableOptions.quality = Quality.Stable;
const stableApp = new Application(stableOptions);
await stableApp!.start();
await stableApp.start();
// Open 3 editors and pin 2 of them
await stableApp.workbench.quickaccess.openFile('www');
@@ -39,10 +49,10 @@ export function setup(stableCodePath: string, testDataPath: string) {
insiderOptions.userDataDir = userDataDir;
const insidersApp = new Application(insiderOptions);
await insidersApp!.start(false /* not expecting walkthrough path */);
await insidersApp.start();
// Verify 3 editors are open
await insidersApp.workbench.editors.waitForEditorFocus('Untitled-1');
await insidersApp.workbench.editors.selectTab('Untitled-1');
await insidersApp.workbench.editors.selectTab('app.js');
await insidersApp.workbench.editors.selectTab('www');
@@ -50,6 +60,7 @@ export function setup(stableCodePath: string, testDataPath: string) {
});
it(`verifies that 'hot exit' works for dirty files`, async function () {
const stableCodePath = opts['stable-build'];
if (!stableCodePath) {
this.skip();
}
@@ -62,7 +73,7 @@ export function setup(stableCodePath: string, testDataPath: string) {
stableOptions.quality = Quality.Stable;
const stableApp = new Application(stableOptions);
await stableApp!.start();
await stableApp.start();
await stableApp.workbench.editors.newUntitledFile();
@@ -75,15 +86,18 @@ export function setup(stableCodePath: string, testDataPath: string) {
await stableApp.workbench.quickaccess.openFile(readmeMd);
await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType);
await timeout(2000); // give time to store the backup before stopping the app
await stableApp.stop();
const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions);
insiderOptions.userDataDir = userDataDir;
const insidersApp = new Application(insiderOptions);
await insidersApp!.start(false /* not expecting walkthrough path */);
await insidersApp.start();
await insidersApp.workbench.editors.waitForActiveTab(readmeMd, true);
await insidersApp.workbench.editors.waitForTab(readmeMd, true);
await insidersApp.workbench.editors.selectTab(readmeMd);
await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1);
await insidersApp.workbench.editors.waitForTab(untitled, true);

View File

@@ -3,46 +3,40 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Application, Quality } from '../../../../automation';
import { afterSuite, beforeSuite } from '../../utils';
export function setup() {
export function setup(opts: minimist.ParsedArgs) {
describe('Localization', () => {
before(async function () {
const app = this.app as Application;
// Don't run the localization tests in dev or remote.
if (app.quality === Quality.Dev || app.remote) {
return;
}
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false);
await app.restart({ extraArgs: ['--locale=DE'] });
});
beforeSuite(opts);
afterSuite(opts);
it(`starts with 'DE' locale and verifies title and viewlets text is in German`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev || app.remote) {
this.skip();
return;
return this.skip();
}
// await app.workbench.explorer.waitForOpenEditorsViewTitle(title => /geöffnete editoren/i.test(title));
await app.workbench.extensions.openExtensionsViewlet();
await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', false);
await app.restart({ extraArgs: ['--locale=DE'] });
await app.workbench.search.openSearchViewlet();
await app.workbench.search.waitForTitle(title => /suchen/i.test(title));
const result = await app.workbench.localization.getLocalizedStrings();
const localeInfo = await app.workbench.localization.getLocaleInfo();
// await app.workbench.scm.openSCMViewlet();
// await app.workbench.scm.waitForTitle(title => /quellcodeverwaltung/i.test(title));
if (localeInfo.locale === undefined || localeInfo.locale.toLowerCase() !== 'de') {
throw new Error(`The requested locale for VS Code was not German. The received value is: ${localeInfo.locale === undefined ? 'not set' : localeInfo.locale}`);
}
// See https://github.com/microsoft/vscode/issues/93462
// await app.workbench.debug.openDebugViewlet();
// await app.workbench.debug.waitForTitle(title => /starten/i.test(title));
if (localeInfo.language.toLowerCase() !== 'de') {
throw new Error(`The UI language is not German. It is ${localeInfo.language}`);
}
// await app.workbench.extensions.openExtensionsViewlet();
// await app.workbench.extensions.waitForTitle(title => /extensions/i.test(title));
if (result.open.toLowerCase() !== 'öffnen' || result.close.toLowerCase() !== 'schließen' || result.find.toLowerCase() !== 'finden') {
throw new Error(`Received wrong German localized strings: ${JSON.stringify(result, undefined, 0)}`);
}
});
});
}

View File

@@ -6,20 +6,14 @@
import * as fs from 'fs';
import * as cp from 'child_process';
import * as path from 'path';
import * as os from 'os';
import * as minimist from 'minimist';
import * as tmp from 'tmp';
import * as rimraf from 'rimraf';
import * as mkdirp from 'mkdirp';
import { ncp } from 'ncp';
import {
Application,
Quality,
ApplicationOptions,
MultiLogger,
Logger,
ConsoleLogger,
FileLogger,
} from '../../automation';
import * as vscodetest from 'vscode-test';
import fetch from 'node-fetch';
import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLogger, Application } from '../../automation';
import { main as sqlMain, setup as sqlSetup } from './sql/main'; // {{SQL CARBON EDIT}}
/*import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test';
@@ -35,9 +29,18 @@ import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.te
import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test';
import { setup as setupLaunchTests } from './areas/workbench/launch.test';*/
const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; };
const testDataPath = tmpDir.name;
process.once('exit', () => rimraf.sync(testDataPath));
const testDataPath = path.join(os.tmpdir(), 'vscsmoke');
if (fs.existsSync(testDataPath)) {
rimraf.sync(testDataPath);
}
fs.mkdirSync(testDataPath);
process.once('exit', () => {
try {
rimraf.sync(testDataPath);
} catch {
// noop
}
});
const [, , ...args] = process.argv;
const opts = minimist(args, {
@@ -49,12 +52,14 @@ const opts = minimist(args, {
'test-repo',
'screenshots',
'log',
'extensionsDir' // {{SQL CARBON EDIT}} Let callers control extensions dir for non-packaged extensions
'extensionsDir', // {{SQL CARBON EDIT}} Let callers control extensions dir for non-packaged extensions
'electronArgs'
],
boolean: [
'verbose',
'remote',
'web'
'web',
'headless'
],
default: {
verbose: false
@@ -77,7 +82,6 @@ if (screenshotsPath) {
mkdirp.sync(screenshotsPath);
}
// {{SQL CARBON EDIT}} Add logs to smoke tests
const logPath = opts.log ? path.resolve(opts.log) : null;
if (logPath) {
mkdirp.sync(path.dirname(logPath));
@@ -91,6 +95,12 @@ function fail(errorMessage): void {
const repoPath = path.join(__dirname, '..', '..', '..');
let quality: Quality;
let version: string | undefined;
function parseVersion(version: string): { major: number, minor: number, patch: number } {
const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!;
return { major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch) };
}
//
// #### Electron Smoke Tests ####
@@ -130,17 +140,21 @@ if (!opts.web) {
}
}
function getBuildVersion(root: string): string {
switch (process.platform) {
case 'darwin':
return require(path.join(root, 'Contents', 'Resources', 'app', 'package.json')).version;
default:
return require(path.join(root, 'resources', 'app', 'package.json')).version;
}
}
let testCodePath = opts.build;
let stableCodePath = opts['stable-build'];
let electronPath: string;
let stablePath: string | undefined = undefined;
if (testCodePath) {
electronPath = getBuildElectronPath(testCodePath);
if (stableCodePath) {
stablePath = getBuildElectronPath(stableCodePath);
}
version = getBuildVersion(testCodePath);
} else {
testCodePath = getDevElectronPath();
electronPath = testCodePath;
@@ -153,10 +167,6 @@ if (!opts.web) {
fail(`Can't find VSCode at ${electronPath}.`);
}
if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) {
fail(`Can't find Stable VSCode at ${stablePath}.`);
}
if (process.env.VSCODE_DEV === '1') {
quality = Quality.Dev;
} else if (electronPath.indexOf('Code - Insiders') >= 0 /* macOS/Windows */ || electronPath.indexOf('code-insiders') /* Linux */ >= 0) {
@@ -221,16 +231,66 @@ async function setupRepository(): Promise<void> {
cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath });
}
// None of the test run the project
// None of the current smoke tests have a dependency on the packages.
// If new smoke tests are added that need the packages, uncomment this.
// console.log('*** Running yarn...');
// cp.execSync('yarn', { cwd: workspacePath, stdio: 'inherit' });
}
}
async function ensureStableCode(): Promise<void> {
if (opts.web || !opts['build']) {
return;
}
let stableCodePath = opts['stable-build'];
if (!stableCodePath) {
const { major, minor } = parseVersion(version!);
const majorMinorVersion = `${major}.${minor - 1}`;
const versionsReq = await fetch('https://update.code.visualstudio.com/api/releases/stable', { headers: { 'x-api-version': '2' } });
if (!versionsReq.ok) {
throw new Error('Could not fetch releases from update server');
}
const versions: { version: string }[] = await versionsReq.json();
const prefix = `${majorMinorVersion}.`;
const previousVersion = versions.find(v => v.version.startsWith(prefix));
if (!previousVersion) {
throw new Error(`Could not find suitable stable version ${majorMinorVersion}`);
}
console.log(`*** Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`);
const stableCodeExecutable = await vscodetest.download({
cachePath: path.join(os.tmpdir(), 'vscode-test'),
version: previousVersion.version
});
if (process.platform === 'darwin') {
// Visual Studio Code.app/Contents/MacOS/Electron
stableCodePath = path.dirname(path.dirname(path.dirname(stableCodeExecutable)));
} else {
// VSCode/Code.exe (Windows) | VSCode/code (Linux)
stableCodePath = path.dirname(stableCodeExecutable);
}
}
if (!fs.existsSync(stableCodePath)) {
throw new Error(`Can't find Stable VSCode at ${stableCodePath}.`);
}
console.log(`*** Using stable build ${stableCodePath} for migration tests`);
opts['stable-build'] = stableCodePath;
}
async function setup(): Promise<void> {
console.log('*** Test data:', testDataPath);
console.log('*** Preparing smoketest setup...');
await ensureStableCode();
await setupRepository();
console.log('*** Smoketest setup done!\n');
@@ -249,6 +309,7 @@ function createOptions(): ApplicationOptions {
loggers.push(new FileLogger(opts.log));
log = 'trace';
}
return {
quality,
codePath: opts.build,
@@ -262,7 +323,9 @@ function createOptions(): ApplicationOptions {
screenshotsPath,
remote: opts.remote,
web: opts.web,
browser: opts.browser
headless: opts.headless,
browser: opts.browser,
extraArgs: (opts.electronArgs || '').split(' ').map(a => a.trim()).filter(a => !!a)
};
}
@@ -270,7 +333,7 @@ before(async function () {
this.timeout(2 * 60 * 1000); // allow two minutes for setup
await setup();
this.defaultOptions = createOptions();
await sqlSetup(this.defaultOptions); // {{SQL CARBON EDIT}}
await sqlSetup(this.defaultOptions);
});
after(async function () {
@@ -297,59 +360,38 @@ after(async function () {
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c(undefined)));
});
sqlMain(opts.web);
if (screenshotsPath) {
afterEach(async function () {
if (this.currentTest!.state !== 'failed') {
return;
}
const app = this.app as Application;
const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
await app.captureScreenshot(name);
});
}
if (!opts.web && opts['build'] && !opts['remote']) {
describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing`, () => {
// setupDataMigrationTests(opts, testDataPath); {{SQL CARBON EDIT}} Remove unused tests
});
}
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
if (screenshotsPath) {
afterEach(async function () {
if (this.currentTest!.state !== 'failed') {
return;
}
const app = this.app as Application;
const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
await app.captureScreenshot(name);
});
}
if (opts.log) {
beforeEach(async function () {
const app = this.app as Application;
const title = this.currentTest!.fullTitle();
app.logger.log('*** Test start:', title);
});
}
// if (!opts.web && opts['stable-build']) {
// describe(`Stable vs Insiders Smoke Tests: This test MUST run before releasing by providing the --stable-build command line argument`, () => {
// setupDataMigrationTests(opts['stable-build'], testDataPath);
// });
// }
// {{SQL CARBON EDIT}} - Remove the nested test suite to make sure the suite setup is also applied to beforeEach and afterEach
// describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
before(async function () {
const app = new Application(this.defaultOptions);
await app!.start(opts.web ? false : undefined);
this.app = app;
});
after(async function () {
await this.app.stop();
});
sqlMain(opts.web);
/* if (!opts.web) { setupDataLossTests(); }
if (!opts.web) { setupDataPreferencesTests(); }
setupDataSearchTests();
setupDataNotebookTests();
setupDataLanguagesTests();
setupDataEditorTests();
setupDataStatusbarTests(!!opts.web);
setupDataExtensionTests();
if (!opts.web) { setupDataMultirootTests(); }
if (!opts.web) { setupDataLocalizationTests(); }
if (!opts.web) { setupLaunchTests(); }*/
// });
/* {{SQL CARBON EDIT}} Disable unused tests
if (!opts.web) { setupDataLossTests(opts); }
if (!opts.web) { setupDataPreferencesTests(opts); }
setupDataSearchTests(opts);
setupDataNotebookTests(opts);
setupDataLanguagesTests(opts);
setupDataEditorTests(opts);
setupDataStatusbarTests(opts);
setupDataExtensionTests(opts);
if (!opts.web) { setupDataMultirootTests(opts); }
if (!opts.web) { setupDataLocalizationTests(opts); }
if (!opts.web) { setupLaunchTests(); }
*/
});

View File

@@ -3,7 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import minimist = require('minimist');
import { Suite, Context } from 'mocha';
import { Application, ApplicationOptions } from '../../automation';
export function describeRepeat(n: number, description: string, callback: (this: Suite) => void): void {
for (let i = 0; i < n; i++) {
@@ -16,3 +18,49 @@ export function itRepeat(n: number, description: string, callback: (this: Contex
it(`${description} (iteration ${i})`, callback);
}
}
export function beforeSuite(opts: minimist.ParsedArgs, optionsTransform?: (opts: ApplicationOptions) => Promise<ApplicationOptions>) {
before(async function () {
let options: ApplicationOptions = { ...this.defaultOptions };
if (optionsTransform) {
options = await optionsTransform(options);
}
// https://github.com/microsoft/vscode/issues/34988
const userDataPathSuffix = [...Array(8)].map(() => Math.random().toString(36)[3]).join('');
const userDataDir = options.userDataDir.concat(`-${userDataPathSuffix}`);
const app = new Application({ ...options, userDataDir });
await app.start();
this.app = app;
if (opts.log) {
const title = this.currentTest!.fullTitle();
app.logger.log('*** Test start:', title);
}
});
}
export function afterSuite(opts: minimist.ParsedArgs) {
after(async function () {
const app = this.app as Application;
if (this.currentTest?.state === 'failed' && opts.screenshots) {
const name = this.currentTest!.fullTitle().replace(/[^a-z0-9\-]/ig, '_');
await app.captureScreenshot(name);
}
if (app) {
await app.stop();
}
});
}
export function timeout(i: number) {
return new Promise<void>(resolve => {
setTimeout(() => {
resolve();
}, i);
});
}

View File

@@ -2,6 +2,11 @@
# yarn lockfile v1
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@@ -45,6 +50,14 @@
dependencies:
"@types/node" "*"
"@types/node-fetch@^2.5.10":
version "2.5.10"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132"
integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "13.11.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
@@ -68,6 +81,13 @@
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d"
integrity sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -125,6 +145,11 @@ async-each@^1.0.0:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
atob@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@@ -156,11 +181,24 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
big-integer@^1.6.17:
version "1.6.49"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.49.tgz#f6817d3ea5d4f3fb19e24df9f4b1b4471a8328ce"
integrity sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw==
binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
binary@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=
dependencies:
buffers "~0.1.1"
chainsaw "~0.1.0"
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
@@ -173,6 +211,11 @@ bluebird@^2.9.34:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=
bluebird@~3.4.1:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -206,6 +249,16 @@ braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
buffer-indexof-polyfill@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c"
integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==
buffers@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
cache-base@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -229,6 +282,13 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.0"
chainsaw@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=
dependencies:
traverse ">=0.3.0 <0.4"
chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -284,6 +344,13 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^2.8.1:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -342,6 +409,13 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
debug@4:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -383,6 +457,11 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@@ -416,6 +495,13 @@ domutils@^1.5.1:
dom-serializer "0"
domelementtype "1"
duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
dependencies:
readable-stream "^2.0.2"
duplexer@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@@ -588,6 +674,15 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -608,6 +703,16 @@ fsevents@^1.0.0:
bindings "^1.5.0"
nan "^2.12.1"
fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
dependencies:
graceful-fs "^4.1.2"
inherits "~2.0.0"
mkdirp ">=0.5 0"
rimraf "2"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -673,7 +778,7 @@ glob@^7.1.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
graceful-fs@^4.1.11:
graceful-fs@^4.1.11, graceful-fs@^4.2.2:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
@@ -748,6 +853,23 @@ htmlparser2@^3.9.2:
inherits "^2.0.1"
readable-stream "^3.1.1"
http-proxy-agent@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
dependencies:
"@tootallnate/once" "1"
agent-base "6"
debug "4"
https-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -756,7 +878,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1000,6 +1122,11 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
listenercount@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
@@ -1075,6 +1202,18 @@ micromatch@^3.1.10:
snapdragon "^0.8.1"
to-regex "^3.0.2"
mime-db@1.49.0:
version "1.49.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
mime-types@^2.1.12:
version "2.1.32"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
dependencies:
mime-db "1.49.0"
minimatch@^3.0.2, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -1095,7 +1234,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@^0.5.1:
"mkdirp@>=0.5 0", mkdirp@^0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@@ -1112,6 +1251,11 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nan@^2.12.1:
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@@ -1144,6 +1288,11 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -1234,11 +1383,6 @@ once@^1.3.0:
dependencies:
wrappy "1"
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -1336,7 +1480,7 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
readable-stream@^2.0.2:
readable-stream@^2.0.2, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -1428,13 +1572,20 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rimraf@^2.6.1:
rimraf@2, rimraf@^2.6.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
dependencies:
glob "^7.1.3"
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -1472,6 +1623,11 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
setimmediate@~1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -1644,13 +1800,6 @@ supports-color@^5.3.0:
dependencies:
has-flag "^3.0.0"
tmp@0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
dependencies:
os-tmpdir "~1.0.2"
to-object-path@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -1676,10 +1825,15 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
typescript@3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
"traverse@>=0.3.0 <0.4":
version "0.3.9"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
typescript@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
union-value@^1.0.0:
version "1.0.1"
@@ -1699,6 +1853,22 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
unzipper@^0.10.11:
version "0.10.11"
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e"
integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==
dependencies:
big-integer "^1.6.17"
binary "~0.3.0"
bluebird "~3.4.1"
buffer-indexof-polyfill "~1.0.0"
duplexer2 "~0.1.4"
fstream "^1.0.12"
graceful-fs "^4.2.2"
listenercount "~1.0.1"
readable-stream "~2.3.6"
setimmediate "~1.0.4"
urix@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
@@ -1722,6 +1892,16 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vscode-test@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-1.6.1.tgz#44254c67036de92b00fdd72f6ace5f1854e1a563"
integrity sha512-086q88T2ca1k95mUzffvbzb7esqQNvJgiwY4h29ukPhFo8u+vXOOmelUoU5EQUHs3Of8+JuQ3oGdbVCqaxuTXA==
dependencies:
http-proxy-agent "^4.0.1"
https-proxy-agent "^5.0.0"
rimraf "^3.0.2"
unzipper "^0.10.11"
watch@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c"

View File

@@ -14,6 +14,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter');
const url = require('url');
const minimatch = require('minimatch');
const playwright = require('playwright');
const { applyReporter } = require('../reporter');
// opts
const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec';
@@ -21,8 +22,8 @@ const optimist = require('optimist')
// .describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
.describe('build', 'run with build output (out-build)').boolean('build')
.describe('run', 'only run tests matching <relative_file_path>').string('run')
.describe('glob', 'only run tests matching <glob_pattern>').string('glob')
.describe('debug', 'do not run browsers headless').boolean('debug')
.describe('grep', 'only run tests matching <pattern>').alias('grep', 'g').alias('grep', 'f').string('grep')
.describe('debug', 'do not run browsers headless').alias('debug', ['debug-browser']).boolean('debug')
.describe('browser', 'browsers in which tests should run').string('browser').default('browser', ['chromium', 'firefox', 'webkit'])
.describe('reporter', 'the mocha reporter').string('reporter').default('reporter', defaultReporterName)
.describe('reporter-options', 'the mocha reporter options').string('reporter-options').default('reporter-options', '')
@@ -51,30 +52,7 @@ const withReporter = (function () {
}
}
} else {
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter);
let ctor;
try {
ctor = require(reporterPath);
} catch (err) {
try {
ctor = require(argv.reporter);
} catch (err) {
ctor = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
console.warn(`could not load reporter: ${argv.reporter}, using ${ctor.name}`);
}
}
function parseReporterOption(value) {
let r = /^([^=]+)=(.*)$/.exec(value);
return r ? { [r[1]]: r[2] } : {};
}
let reporterOptions = argv['reporter-options'];
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
return (_, runner) => new ctor(runner, { reporterOptions })
return (_, runner) => applyReporter(runner, argv);
}
})()
@@ -103,7 +81,7 @@ const testModules = (async function () {
} else {
// glob patterns (--glob)
const defaultGlob = '**/*.test.js';
const pattern = argv.glob || defaultGlob
const pattern = argv.run || defaultGlob
isDefaultModules = pattern === defaultGlob;
promise = new Promise((resolve, reject) => {
@@ -146,8 +124,7 @@ function consoleLogFn(msg) {
}
async function runTestsInBrowser(testModules, browserType) {
const args = process.platform === 'linux' && browserType === 'chromium' ? ['--disable-setuid-sandbox'] : undefined; // setuid sandboxes requires root and is used in containers so we disable this to support our CI
const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), args });
const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), devtools: Boolean(argv.debug) });
const context = await browser.newContext();
const page = await context.newPage();
const target = url.pathToFileURL(path.join(__dirname, 'renderer.html'));
@@ -184,7 +161,10 @@ async function runTestsInBrowser(testModules, browserType) {
try {
// @ts-expect-error
await page.evaluate(modules => loadAndRun(modules), testModules);
await page.evaluate(opts => loadAndRun(opts), {
modules: testModules,
grep: argv.grep,
});
} catch (err) {
console.error(err);
}
@@ -236,7 +216,8 @@ class EchoRunner extends events.EventEmitter {
async: runnable.async,
slow: () => runnable.slow,
speed: runnable.speed,
duration: runnable.duration
duration: runnable.duration,
currentRetry: () => runnable.currentRetry,
};
}

View File

@@ -86,7 +86,8 @@
async: runnable.async,
slow: runnable.slow(),
speed: runnable.speed,
duration: runnable.duration
duration: runnable.duration,
currentRetry: runnable.currentRetry(),
};
}
function serializeError(err) {
@@ -114,7 +115,7 @@
runner.on('pending', test => window.mocha_report('pending', serializeRunnable(test)));
};
window.loadAndRun = async function loadAndRun(modules, manual = false) {
window.loadAndRun = async function loadAndRun({ modules, grep }, manual = false) {
// load
await Promise.all(modules.map(module => new Promise((resolve, reject) => {
require([module], resolve, err => {
@@ -132,6 +133,10 @@
// run
return new Promise((resolve, reject) => {
if (grep) {
mocha.grep(grep);
}
if (!manual) {
mocha.reporter(PlaywrightReporter);
}

View File

@@ -17,7 +17,7 @@ const MochaJUnitReporter = require('mocha-junit-reporter');
const url = require('url');
const net = require('net');
const createStatsCollector = require('mocha/lib/stats-collector');
const FullJsonStreamReporter = require('../fullJsonStreamReporter');
const { applyReporter, importMochaReporter } = require('../reporter');
// Disable render process reuse, we still have
// non-context aware native modules in the renderer.
@@ -85,15 +85,6 @@ function deserializeRunnable(runnable) {
};
}
function importMochaReporter(name) {
if (name === 'full-json-stream') {
return FullJsonStreamReporter;
}
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name);
return require(reporterPath);
}
function deserializeError(err) {
const inspect = err.inspect;
err.inspect = () => inspect;
@@ -134,11 +125,6 @@ class IPCRunner extends events.EventEmitter {
}
}
function parseReporterOption(value) {
let r = /^([^=]+)=(.*)$/.exec(value);
return r ? { [r[1]]: r[2] } : {};
}
app.on('ready', () => {
ipcMain.on('error', (_, err) => {
@@ -178,8 +164,7 @@ app.on('ready', () => {
contextIsolation: false,
enableWebSQL: false,
spellcheck: false,
nativeWindowOpen: true,
webviewTag: true
nativeWindowOpen: true
}
});
@@ -214,7 +199,7 @@ app.on('ready', () => {
timeout = setTimeout(() => {
console.error('timed out waiting for before starting tests debugger');
resolve();
}, 7000);
}, 15000);
}).finally(() => {
if (socket) {
socket.end();
@@ -259,23 +244,7 @@ app.on('ready', () => {
});
}
let Reporter;
try {
Reporter = importMochaReporter(argv.reporter);
} catch (err) {
try {
Reporter = require(argv.reporter);
} catch (err) {
Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`);
}
}
let reporterOptions = argv['reporter-options'];
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
new Reporter(runner, { reporterOptions });
applyReporter(runner, argv);
}
if (!argv.debug) {

View File

@@ -250,6 +250,10 @@ function serializeError(err) {
function safeStringify(obj) {
const seen = new Set();
return JSON.stringify(obj, (key, value) => {
if (value === undefined) {
return '[undefined]';
}
if (isObject(value) || Array.isArray(value)) {
if (seen.has(value)) {
return '[Circular]';

View File

@@ -7,6 +7,7 @@ const { constants } = require('mocha/lib/runner');
const BaseRunner = require('mocha/lib/reporters/base');
const {
EVENT_TEST_BEGIN,
EVENT_TEST_PASS,
EVENT_TEST_FAIL,
EVENT_RUN_BEGIN,
@@ -28,6 +29,7 @@ module.exports = class FullJsonStreamReporter extends BaseRunner {
runner.once(EVENT_RUN_BEGIN, () => writeEvent(['start', { total }]));
runner.once(EVENT_RUN_END, () => writeEvent(['end', this.stats]));
runner.on(EVENT_TEST_BEGIN, test => writeEvent(['testStart', clean(test)]));
runner.on(EVENT_TEST_PASS, test => writeEvent(['pass', clean(test)]));
runner.on(EVENT_TEST_FAIL, (test, err) => {
test = clean(test);

42
test/unit/reporter.js Normal file
View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const mocha = require('mocha');
const FullJsonStreamReporter = require('./fullJsonStreamReporter');
const path = require('path');
function parseReporterOption(value) {
let r = /^([^=]+)=(.*)$/.exec(value);
return r ? { [r[1]]: r[2] } : {};
}
exports.importMochaReporter = name => {
if (name === 'full-json-stream') {
return FullJsonStreamReporter;
}
const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', name);
return require(reporterPath);
}
exports.applyReporter = (runner, argv) => {
let Reporter;
try {
Reporter = exports.importMochaReporter(argv.reporter);
} catch (err) {
try {
Reporter = require(argv.reporter);
} catch (err) {
Reporter = process.platform === 'win32' ? mocha.reporters.List : mocha.reporters.Spec;
console.warn(`could not load reporter: ${argv.reporter}, using ${Reporter.name}`);
}
}
let reporterOptions = argv['reporter-options'];
reporterOptions = typeof reporterOptions === 'string' ? [reporterOptions] : reporterOptions;
reporterOptions = reporterOptions.reduce((r, o) => Object.assign(r, parseReporterOption(o)), {});
return new Reporter(runner, { reporterOptions });
}