Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/test/automation/src/playwrightBrowser.ts
5241 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as playwright from '@playwright/test';
7
import { ChildProcess, spawn } from 'child_process';
8
import { join } from 'path';
9
import * as fs from 'fs';
10
import { URI } from 'vscode-uri';
11
import { Logger, measureAndLog } from './logger';
12
import type { LaunchOptions } from './code';
13
import { PlaywrightDriver } from './playwrightDriver';
14
15
const root = join(__dirname, '..', '..', '..');
16
17
let port = 9000;
18
19
export async function launch(options: LaunchOptions): Promise<{ serverProcess: ChildProcess; driver: PlaywrightDriver }> {
20
21
// Launch server
22
const { serverProcess, endpoint } = await launchServer(options);
23
24
// Launch browser
25
const { browser, context, page, pageLoadedPromise } = await launchBrowser(options, endpoint);
26
27
return {
28
serverProcess,
29
driver: new PlaywrightDriver(browser, context, page, serverProcess, pageLoadedPromise, options)
30
};
31
}
32
33
async function launchServer(options: LaunchOptions) {
34
const { userDataDir, codePath, extensionsPath, logger, logsPath } = options;
35
const serverLogsPath = join(logsPath, 'server');
36
const codeServerPath = codePath ?? process.env.VSCODE_REMOTE_SERVER_PATH;
37
const agentFolder = userDataDir;
38
39
const env = {
40
VSCODE_REMOTE_SERVER_PATH: codeServerPath,
41
...process.env
42
};
43
44
const args = [
45
'--disable-telemetry',
46
'--disable-experiments',
47
'--disable-workspace-trust',
48
`--port=${port++}`,
49
'--enable-smoke-test-driver',
50
'--accept-server-license-terms',
51
`--logsPath=${serverLogsPath}`
52
];
53
if (agentFolder) {
54
await measureAndLog(() => fs.promises.mkdir(agentFolder, { recursive: true }), `mkdirp(${agentFolder})`, logger);
55
args.push(`--server-data-dir=${agentFolder}`);
56
}
57
if (extensionsPath) {
58
args.push(`--extensions-dir=${extensionsPath}`);
59
}
60
61
if (options.verbose) {
62
args.push('--log=trace');
63
}
64
if (options.extensionDevelopmentPath) {
65
args.push(`--extensionDevelopmentPath=${options.extensionDevelopmentPath}`);
66
}
67
68
let serverLocation: string | undefined;
69
if (codeServerPath) {
70
const { serverApplicationName } = require(join(codeServerPath, 'product.json'));
71
serverLocation = join(codeServerPath, 'bin', `${serverApplicationName}${process.platform === 'win32' ? '.cmd' : ''}`);
72
73
logger.log(`Starting built server from '${serverLocation}'`);
74
} else {
75
serverLocation = join(root, `scripts/code-server.${process.platform === 'win32' ? 'bat' : 'sh'}`);
76
77
logger.log(`Starting server out of sources from '${serverLocation}'`);
78
}
79
80
logger.log(`Storing log files into '${serverLogsPath}'`);
81
82
logger.log(`Command line: '${serverLocation}' ${args.join(' ')}`);
83
const shell: boolean = (process.platform === 'win32');
84
const serverProcess = spawn(
85
serverLocation,
86
args,
87
{ env, shell }
88
);
89
90
logger.log(`Started server for browser smoke tests (pid: ${serverProcess.pid})`);
91
92
return {
93
serverProcess,
94
endpoint: await measureAndLog(() => waitForEndpoint(serverProcess, logger), 'waitForEndpoint(serverProcess)', logger)
95
};
96
}
97
98
async function launchBrowser(options: LaunchOptions, endpoint: string) {
99
const { logger, workspacePath, tracing, snapshots, headless } = options;
100
101
const playwrightImpl = options.playwright ?? playwright;
102
const [browserType, browserChannel] = (options.browser ?? 'chromium').split('-');
103
const browser = await measureAndLog(() => playwrightImpl[browserType as unknown as 'chromium' | 'webkit' | 'firefox'].launch({
104
headless: headless ?? false,
105
timeout: 0,
106
channel: browserChannel,
107
}), 'playwright#launch', logger);
108
109
browser.on('disconnected', () => logger.log(`Playwright: browser disconnected`));
110
111
const context = await measureAndLog(
112
() => browser.newContext({
113
recordVideo: options.videosPath
114
? {
115
dir: options.videosPath,
116
size: { width: 1920, height: 1080 }
117
} : undefined,
118
}),
119
'browser.newContext',
120
logger
121
);
122
123
if (tracing) {
124
try {
125
await measureAndLog(() => context.tracing.start({ screenshots: true, snapshots }), 'context.tracing.start()', logger);
126
} catch (error) {
127
logger.log(`Playwright (Browser): Failed to start playwright tracing (${error})`); // do not fail the build when this fails
128
}
129
}
130
131
const page = await measureAndLog(() => context.newPage(), 'context.newPage()', logger);
132
await measureAndLog(() => page.setViewportSize({ width: 1440, height: 900 }), 'page.setViewportSize', logger);
133
134
if (options.verbose) {
135
context.on('page', () => logger.log(`Playwright (Browser): context.on('page')`));
136
context.on('requestfailed', e => logger.log(`Playwright (Browser): context.on('requestfailed') [${e.failure()?.errorText} for ${e.url()}]`));
137
138
page.on('console', e => logger.log(`Playwright (Browser): window.on('console') [${e.text()}]`));
139
page.on('dialog', () => logger.log(`Playwright (Browser): page.on('dialog')`));
140
page.on('domcontentloaded', () => logger.log(`Playwright (Browser): page.on('domcontentloaded')`));
141
page.on('load', () => logger.log(`Playwright (Browser): page.on('load')`));
142
page.on('popup', () => logger.log(`Playwright (Browser): page.on('popup')`));
143
page.on('framenavigated', () => logger.log(`Playwright (Browser): page.on('framenavigated')`));
144
page.on('requestfailed', e => logger.log(`Playwright (Browser): page.on('requestfailed') [${e.failure()?.errorText} for ${e.url()}]`));
145
}
146
147
page.on('pageerror', async (error) => logger.log(`Playwright (Browser) ERROR: page error: ${error}`));
148
page.on('crash', () => logger.log('Playwright (Browser) ERROR: page crash'));
149
page.on('close', () => logger.log('Playwright (Browser): page close'));
150
page.on('response', async (response) => {
151
if (response.status() >= 400) {
152
logger.log(`Playwright (Browser) ERROR: HTTP status ${response.status()} for ${response.url()}`);
153
}
154
});
155
156
const payloadParam = `[${[
157
'["enableProposedApi",""]',
158
'["skipWelcome", "true"]',
159
'["skipReleaseNotes", "true"]',
160
`["logLevel","${options.verbose ? 'trace' : 'info'}"]`
161
].join(',')}]`;
162
163
// Build URL with optional workspace path
164
let url = `${endpoint}&`;
165
if (workspacePath) {
166
const workspaceParam = workspacePath.endsWith('.code-workspace') ? 'workspace' : 'folder';
167
url += `${workspaceParam}=${URI.file(workspacePath).path}&`;
168
}
169
url += `payload=${payloadParam}`;
170
171
const gotoPromise = measureAndLog(() => page.goto(url), 'page.goto()', logger);
172
const pageLoadedPromise = page.waitForLoadState('load');
173
174
await gotoPromise;
175
176
return { browser, context, page, pageLoadedPromise };
177
}
178
179
function waitForEndpoint(server: ChildProcess, logger: Logger): Promise<string> {
180
return new Promise<string>((resolve, reject) => {
181
let endpointFound = false;
182
183
server.stdout?.on('data', data => {
184
if (!endpointFound) {
185
logger.log(`[server] stdout: ${data}`); // log until endpoint found to diagnose issues
186
}
187
188
const matches = data.toString('ascii').match(/Web UI available at (.+)/);
189
if (matches !== null) {
190
endpointFound = true;
191
192
resolve(matches[1]);
193
}
194
});
195
196
server.stderr?.on('data', error => {
197
if (!endpointFound) {
198
logger.log(`[server] stderr: ${error}`); // log until endpoint found to diagnose issues
199
}
200
201
if (error.toString().indexOf('EADDRINUSE') !== -1) {
202
reject(new Error(error));
203
}
204
});
205
});
206
}
207
208