mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-02 00:53:32 +03:00
chore: add test server stub (#29568)
This commit is contained in:
parent
84fefdaac6
commit
d573c515a3
@ -40,6 +40,7 @@ export * from './time';
|
||||
export * from './timeoutRunner';
|
||||
export * from './traceUtils';
|
||||
export * from './userAgent';
|
||||
export * from './wsServer';
|
||||
export * from './zipFile';
|
||||
export * from './zones';
|
||||
export * from './isomorphic/locatorGenerators';
|
||||
|
@ -33,12 +33,12 @@ type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _ctWorker: { context: BrowserContext | undefined, hash: string } };
|
||||
type BaseTestFixtures = {
|
||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>,
|
||||
_contextReuseMode: ContextReuseMode
|
||||
_optionContextReuseMode: ContextReuseMode
|
||||
};
|
||||
|
||||
export const fixtures: Fixtures<TestFixtures, WorkerFixtures, BaseTestFixtures> = {
|
||||
|
||||
_contextReuseMode: 'when-possible',
|
||||
_optionContextReuseMode: 'when-possible',
|
||||
|
||||
serviceWorkers: 'block',
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
"./lib/transform/babelBundle": "./lib/transform/babelBundle.js",
|
||||
"./lib/transform/compilationCache": "./lib/transform/compilationCache.js",
|
||||
"./lib/runner/runner": "./lib/runner/runner.js",
|
||||
"./lib/runner/testServer": "./lib/runner/testServer.js",
|
||||
"./lib/transform/esmLoader": "./lib/transform/esmLoader.js",
|
||||
"./lib/transform/transform": "./lib/transform/transform.js",
|
||||
"./lib/internalsForTest": "./lib/internalsForTest.js",
|
||||
|
@ -40,6 +40,7 @@ export class FullConfigInternal {
|
||||
readonly configDir: string;
|
||||
readonly configCLIOverrides: ConfigCLIOverrides;
|
||||
readonly ignoreSnapshots: boolean;
|
||||
readonly preserveOutputDir: boolean;
|
||||
readonly webServers: Exclude<FullConfig['webServer'], null>[];
|
||||
readonly plugins: TestRunnerPluginRegistration[];
|
||||
readonly projects: FullProjectInternal[] = [];
|
||||
@ -68,6 +69,7 @@ export class FullConfigInternal {
|
||||
this.configDir = configDir;
|
||||
this.configCLIOverrides = configCLIOverrides;
|
||||
this.globalOutputDir = takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, config.outputDir), throwawayArtifactsPath, path.resolve(process.cwd()));
|
||||
this.preserveOutputDir = configCLIOverrides.preserveOutputDir || false;
|
||||
this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots, false);
|
||||
const privateConfiguration = (config as any)['@playwright/test'];
|
||||
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
|
||||
|
@ -25,6 +25,7 @@ export type ConfigCLIOverrides = {
|
||||
globalTimeout?: number;
|
||||
maxFailures?: number;
|
||||
outputDir?: string;
|
||||
preserveOutputDir?: boolean;
|
||||
quiet?: boolean;
|
||||
repeatEach?: number;
|
||||
retries?: number;
|
||||
|
@ -44,14 +44,16 @@ if ((process as any)['__pw_initiator__']) {
|
||||
|
||||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
_combinedContextOptions: BrowserContextOptions,
|
||||
_contextReuseMode: ContextReuseMode,
|
||||
_reuseContext: boolean,
|
||||
_setupContextOptions: void;
|
||||
_setupArtifacts: void;
|
||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
|
||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||
_browserOptions: LaunchOptions;
|
||||
_optionContextReuseMode: ContextReuseMode,
|
||||
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
|
||||
_reuseContext: boolean,
|
||||
};
|
||||
|
||||
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
@ -63,8 +65,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }],
|
||||
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }],
|
||||
launchOptions: [{}, { scope: 'worker', option: true }],
|
||||
connectOptions: [async ({}, use) => {
|
||||
await use(connectOptionsFromEnv());
|
||||
connectOptions: [async ({ _optionConnectOptions }, use) => {
|
||||
await use(connectOptionsFromEnv() || _optionConnectOptions);
|
||||
}, { scope: 'worker', option: true }],
|
||||
screenshot: ['off', { scope: 'worker', option: true }],
|
||||
video: ['off', { scope: 'worker', option: true }],
|
||||
@ -88,7 +90,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
(browserType as any)._defaultLaunchOptions = undefined;
|
||||
}, { scope: 'worker', auto: true }],
|
||||
|
||||
browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, testInfo) => {
|
||||
browser: [async ({ playwright, browserName, _browserOptions, connectOptions, _reuseContext }, use, testInfo) => {
|
||||
if (!['chromium', 'firefox', 'webkit'].includes(browserName))
|
||||
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
||||
|
||||
@ -97,7 +99,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
...connectOptions,
|
||||
exposeNetwork: connectOptions.exposeNetwork ?? (connectOptions as any)._exposeNetwork,
|
||||
headers: {
|
||||
...(process.env.PW_TEST_REUSE_CONTEXT ? { 'x-playwright-reuse-context': '1' } : {}),
|
||||
...(_reuseContext ? { 'x-playwright-reuse-context': '1' } : {}),
|
||||
// HTTP headers are ASCII only (not UTF-8).
|
||||
'x-playwright-launch-options': jsonStringifyForceASCII(_browserOptions),
|
||||
...connectOptions.headers,
|
||||
@ -348,12 +350,16 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
|
||||
}, { scope: 'test', _title: 'context' } as any],
|
||||
|
||||
_contextReuseMode: process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : (process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none'),
|
||||
_optionContextReuseMode: ['none', { scope: 'worker' }],
|
||||
_optionConnectOptions: [undefined, { scope: 'worker' }],
|
||||
|
||||
_reuseContext: [async ({ video, _contextReuseMode }, use, testInfo) => {
|
||||
const reuse = _contextReuseMode === 'force' || (_contextReuseMode === 'when-possible' && !shouldCaptureVideo(normalizeVideoMode(video), testInfo));
|
||||
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
||||
let mode = _optionContextReuseMode;
|
||||
if (process.env.PW_TEST_REUSE_CONTEXT)
|
||||
mode = process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : (process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none');
|
||||
const reuse = mode === 'force' || (mode === 'when-possible' && normalizeVideoMode(video) === 'off');
|
||||
await use(reuse);
|
||||
}, { scope: 'test', _title: 'context' } as any],
|
||||
}, { scope: 'worker', _title: 'context' } as any],
|
||||
|
||||
context: async ({ playwright, browser, _reuseContext, _contextFactory }, use, testInfo) => {
|
||||
attachConnectedHeaderIfNeeded(testInfo, browser);
|
||||
|
@ -35,6 +35,7 @@ export { program } from 'playwright-core/lib/cli/program';
|
||||
import type { ReporterDescription } from '../types/test';
|
||||
import { prepareErrorStack } from './reporters/base';
|
||||
import { affectedTestFiles, cacheDir } from './transform/compilationCache';
|
||||
import { runTestServer } from './runner/testServer';
|
||||
|
||||
function addTestCommand(program: Command) {
|
||||
const command = program.command('test [test-filter...]');
|
||||
@ -115,6 +116,15 @@ function addFindRelatedTestFilesCommand(program: Command) {
|
||||
});
|
||||
}
|
||||
|
||||
function addTestServerCommand(program: Command) {
|
||||
const command = program.command('test-server', { hidden: true });
|
||||
command.description('start test server');
|
||||
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||
command.action(options => {
|
||||
void runTestServer(options.config);
|
||||
});
|
||||
}
|
||||
|
||||
function addShowReportCommand(program: Command) {
|
||||
const command = program.command('show-report [report]');
|
||||
command.description('show HTML report');
|
||||
@ -339,3 +349,4 @@ addListFilesCommand(program);
|
||||
addMergeReportsCommand(program);
|
||||
addClearCacheCommand(program);
|
||||
addFindRelatedTestFilesCommand(program);
|
||||
addTestServerCommand(program);
|
||||
|
@ -88,7 +88,8 @@ function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfig
|
||||
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
|
||||
if (config.config.globalSetup || config.config.globalTeardown)
|
||||
taskRunner.addTask('global setup', createGlobalSetupTask());
|
||||
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
|
||||
if (!config.configCLIOverrides.preserveOutputDir && !process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS)
|
||||
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
|
||||
}
|
||||
|
||||
function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
|
||||
@ -165,8 +166,6 @@ function createGlobalSetupTask(): Task<TestRun> {
|
||||
function createRemoveOutputDirsTask(): Task<TestRun> {
|
||||
return {
|
||||
setup: async ({ config }) => {
|
||||
if (process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS)
|
||||
return;
|
||||
const outputDirs = new Set<string>();
|
||||
const projects = filterProjects(config.projects, config.cliProjectFilter);
|
||||
projects.forEach(p => outputDirs.add(p.project.outputDir));
|
||||
|
125
packages/playwright/src/runner/testServer.ts
Normal file
125
packages/playwright/src/runner/testServer.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type http from 'http';
|
||||
import { ManualPromise, createGuid } from 'playwright-core/lib/utils';
|
||||
import { WSServer } from 'playwright-core/lib/utils';
|
||||
import type { WebSocket } from 'playwright-core/lib/utilsBundle';
|
||||
import type { FullResult } from 'playwright/types/testReporter';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { loadConfigFromFile } from '../common/configLoader';
|
||||
import { InternalReporter } from '../reporters/internalReporter';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
import { createReporters } from './reporters';
|
||||
import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch } from './tasks';
|
||||
|
||||
type PlaywrightTestOptions = {
|
||||
headed?: boolean,
|
||||
oneWorker?: boolean,
|
||||
trace?: 'on' | 'off',
|
||||
projects?: string[];
|
||||
grep?: string;
|
||||
reuseContext?: boolean,
|
||||
connectWsEndpoint?: string;
|
||||
};
|
||||
|
||||
export async function runTestServer(configFile: string) {
|
||||
process.env.PW_TEST_HTML_REPORT_OPEN = 'never';
|
||||
process.env.FORCE_COLOR = '1';
|
||||
|
||||
const config = await loadConfigFromFile(configFile);
|
||||
if (!config)
|
||||
return;
|
||||
|
||||
const dispatcher = new Dispatcher(config);
|
||||
const wss = new WSServer({
|
||||
onConnection(request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) {
|
||||
ws.on('message', async message => {
|
||||
const { id, method, params } = JSON.parse(message.toString());
|
||||
const result = await (dispatcher as any)[method](params);
|
||||
ws.send(JSON.stringify({ id, result }));
|
||||
});
|
||||
return {
|
||||
async close() {}
|
||||
};
|
||||
},
|
||||
});
|
||||
const url = await wss.listen(0, 'localhost', '/' + createGuid());
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Listening on ${url}`);
|
||||
}
|
||||
|
||||
class Dispatcher {
|
||||
private _config: FullConfigInternal;
|
||||
|
||||
constructor(config: FullConfigInternal) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
async test(params: { mode: 'list' | 'run', locations: string[], options: PlaywrightTestOptions, reporter: string, env: NodeJS.ProcessEnv }) {
|
||||
for (const name in params.env)
|
||||
process.env[name] = params.env[name];
|
||||
if (params.mode === 'list')
|
||||
await listTests(this._config, params.reporter, params.locations);
|
||||
if (params.mode === 'run')
|
||||
await runTests(this._config, params.reporter, params.locations, params.options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function listTests(config: FullConfigInternal, reporterPath: string, locations: string[] | undefined) {
|
||||
config.cliArgs = [...(locations || []), '--reporter=null'];
|
||||
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, 'list', [[reporterPath]])));
|
||||
const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: false });
|
||||
const testRun = new TestRun(config, reporter);
|
||||
reporter.onConfigure(config.config);
|
||||
|
||||
const taskStatus = await taskRunner.run(testRun, 0);
|
||||
let status: FullResult['status'] = testRun.failureTracker.result();
|
||||
if (status === 'passed' && taskStatus !== 'passed')
|
||||
status = taskStatus;
|
||||
const modifiedResult = await reporter.onEnd({ status });
|
||||
if (modifiedResult && modifiedResult.status)
|
||||
status = modifiedResult.status;
|
||||
await reporter.onExit();
|
||||
}
|
||||
|
||||
async function runTests(config: FullConfigInternal, reporterPath: string, locations: string[] | undefined, options: PlaywrightTestOptions) {
|
||||
config.cliArgs = locations || [];
|
||||
config.cliGrep = options.grep;
|
||||
config.cliProjectFilter = options.projects;
|
||||
|
||||
config.configCLIOverrides.reporter = [[reporterPath]];
|
||||
config.configCLIOverrides.repeatEach = 1;
|
||||
config.configCLIOverrides.retries = 0;
|
||||
config.configCLIOverrides.workers = options.oneWorker ? 1 : undefined;
|
||||
config.configCLIOverrides.preserveOutputDir = true;
|
||||
config.configCLIOverrides.use = {
|
||||
trace: options.trace,
|
||||
headless: options.headed ? false : undefined,
|
||||
_optionContextReuseMode: options.reuseContext ? 'when-possible' : undefined,
|
||||
_optionConnectOptions: options.connectWsEndpoint ? { wsEndpoint: options.connectWsEndpoint } : undefined,
|
||||
};
|
||||
|
||||
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, 'run')));
|
||||
const taskRunner = createTaskRunnerForWatch(config, reporter);
|
||||
const testRun = new TestRun(config, reporter);
|
||||
reporter.onConfigure(config.config);
|
||||
const stop = new ManualPromise();
|
||||
const status = await taskRunner.run(testRun, 0, stop);
|
||||
await reporter.onEnd({ status });
|
||||
await reporter.onExit();
|
||||
}
|
@ -417,15 +417,20 @@ async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: nu
|
||||
config.config.workers = 1;
|
||||
showBrowserServer = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: 1 });
|
||||
const wsEndpoint = await showBrowserServer.listen();
|
||||
process.env.PW_TEST_REUSE_CONTEXT = '1';
|
||||
process.env.PW_TEST_CONNECT_WS_ENDPOINT = wsEndpoint;
|
||||
config.configCLIOverrides.use = {
|
||||
...config.configCLIOverrides.use,
|
||||
_optionContextReuseMode: 'when-possible',
|
||||
_optionConnectOptions: { wsEndpoint },
|
||||
};
|
||||
process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('on')}\n`);
|
||||
} else {
|
||||
config.config.workers = originalWorkers;
|
||||
if (config.configCLIOverrides.use) {
|
||||
delete config.configCLIOverrides.use._optionContextReuseMode;
|
||||
delete config.configCLIOverrides.use._optionConnectOptions;
|
||||
}
|
||||
await showBrowserServer?.close();
|
||||
showBrowserServer = undefined;
|
||||
delete process.env.PW_TEST_REUSE_CONTEXT;
|
||||
delete process.env.PW_TEST_CONNECT_WS_ENDPOINT;
|
||||
process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('off')}\n`);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user