mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-27 09:35:37 -05:00
* 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>
226 lines
7.6 KiB
TypeScript
226 lines
7.6 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
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';
|
|
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',
|
|
shift: 'Shift',
|
|
enter: 'Enter',
|
|
escape: 'Escape',
|
|
right: 'ArrowRight',
|
|
up: 'ArrowUp',
|
|
down: 'ArrowDown',
|
|
left: 'ArrowLeft',
|
|
home: 'Home',
|
|
esc: 'Escape'
|
|
};
|
|
|
|
let traceCounter = 1;
|
|
|
|
function buildDriver(browser: playwright.Browser, context: playwright.BrowserContext, page: playwright.Page): IDriver {
|
|
const driver: IDriver = {
|
|
_serviceBrand: undefined,
|
|
getWindowIds: () => {
|
|
return Promise.resolve([1]);
|
|
},
|
|
// {{SQL CARBON EDIT}}
|
|
capturePage: async () => {
|
|
const buffer = await page.screenshot();
|
|
return buffer.toString('base64');
|
|
},
|
|
reloadWindow: (windowId) => Promise.resolve(),
|
|
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++) {
|
|
const chord = chords[i];
|
|
if (i > 0) {
|
|
await timeout(100);
|
|
}
|
|
const keys = chord.split('+');
|
|
const keysDown: string[] = [];
|
|
for (let i = 0; i < keys.length; i++) {
|
|
if (keys[i] in vscodeToPlaywrightKey) {
|
|
keys[i] = vscodeToPlaywrightKey[keys[i]];
|
|
}
|
|
await page.keyboard.down(keys[i]);
|
|
keysDown.push(keys[i]);
|
|
}
|
|
while (keysDown.length > 0) {
|
|
await page.keyboard.up(keysDown.pop()!);
|
|
}
|
|
}
|
|
|
|
await timeout(100);
|
|
},
|
|
click: async (windowId, selector, xoffset, yoffset) => {
|
|
const { x, y } = await driver.getElementXY(windowId, selector, xoffset, yoffset);
|
|
await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
|
|
},
|
|
doubleClick: async (windowId, selector) => {
|
|
await driver.click(windowId, selector, 0, 0);
|
|
await timeout(60);
|
|
await driver.click(windowId, selector, 0, 0);
|
|
await timeout(100);
|
|
},
|
|
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})`),
|
|
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}')`),
|
|
getLocaleInfo: (windowId) => page.evaluate(`window.driver.getLocaleInfo()`),
|
|
getLocalizedStrings: (windowId) => page.evaluate(`window.driver.getLocalizedStrings()`)
|
|
};
|
|
return driver;
|
|
}
|
|
|
|
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, verbose: boolean): Promise<void> {
|
|
workspacePath = _workspacePath;
|
|
|
|
const agentFolder = userDataDir;
|
|
await promisify(mkdir)(agentFolder);
|
|
const env = {
|
|
VSCODE_AGENT_FOLDER: agentFolder,
|
|
VSCODE_REMOTE_SERVER_PATH: codeServerPath,
|
|
...process.env
|
|
};
|
|
|
|
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'}`);
|
|
args.push(`--logsPath=${logsPath}`);
|
|
|
|
if (verbose) {
|
|
console.log(`Starting built server from '${serverLocation}'`);
|
|
console.log(`Storing log files into '${logsPath}'`);
|
|
}
|
|
} else {
|
|
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 }
|
|
);
|
|
|
|
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();
|
|
}
|
|
|
|
async function teardown(): Promise<void> {
|
|
if (server) {
|
|
try {
|
|
await new Promise<void>((c, e) => kill(server!.pid, err => err ? e(err) : c()));
|
|
} catch {
|
|
// noop
|
|
}
|
|
|
|
server = undefined;
|
|
}
|
|
}
|
|
|
|
function waitForEndpoint(): Promise<string> {
|
|
return new Promise<string>(r => {
|
|
server!.stdout?.on('data', (d: Buffer) => {
|
|
const matches = d.toString('ascii').match(/Web UI available at (.+)/);
|
|
if (matches !== null) {
|
|
r(matches[1]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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[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 });
|
|
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, context, page)
|
|
};
|
|
c(result);
|
|
});
|
|
}
|