feat(test runner): support connectOptions (#11919)

This allows to specify `connectOptions` in the config that
switch built-in `browser` to be remotely connected.
This commit is contained in:
Dmitry Gozman 2022-02-08 20:45:42 -08:00 committed by GitHub
parent 5881a46ecf
commit 19368e93af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 1 deletions

View File

@ -110,6 +110,13 @@ Learn more about [various timeouts](./test-timeouts.md).
## property: TestOptions.colorScheme = %%-context-option-colorscheme-%%
## property: TestOptions.connectOptions
- type: <[void]|[Object]>
- `wsEndpoint` <[string]> A browser websocket endpoint to connect to.
- `headers` <[void]|[Object]<[string], [string]>> Additional HTTP headers to be sent with web socket connect request. Optional.
When connect options are specified, default [`property: Fixtures.browser`], [`property: Fixtures.context`] and [`property: Fixtures.page`] use the remote browser instead of launching a browser locally, and any launch options like [`property: TestOptions.headless`] or [`property: TestOptions.channel`] are ignored.
## property: TestOptions.contextOptions
- type: <[Object]>

View File

@ -63,6 +63,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
maxPayload: 256 * 1024 * 1024, // 256Mb,
handshakeTimeout: params.timeout,
headers: paramsHeaders,
followRedirects: true,
});
const pipe = new JsonPipeDispatcher(this._scope);
const openPromise = new ManualPromise<{ pipe: JsonPipeDispatcher }>();

View File

@ -70,6 +70,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
headless: [ undefined, { scope: 'worker', option: true } ],
channel: [ undefined, { scope: 'worker', option: true } ],
launchOptions: [ {}, { scope: 'worker', option: true } ],
connectOptions: [ undefined, { scope: 'worker', option: true } ],
screenshot: [ 'off', { scope: 'worker', option: true } ],
video: [ 'off', { scope: 'worker', option: true } ],
trace: [ 'off', { scope: 'worker', option: true } ],
@ -100,9 +101,16 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
await use(options);
}, { scope: 'worker' }],
browser: [async ({ playwright, browserName }, use) => {
browser: [async ({ playwright, browserName, connectOptions }, use) => {
if (!['chromium', 'firefox', 'webkit'].includes(browserName))
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
if (connectOptions) {
const browser = await playwright[browserName].connect(connectOptions);
await use(browser);
await browser.close();
return;
}
const browser = await playwright[browserName].launch();
await use(browser);
await browser.close();

View File

@ -2658,6 +2658,17 @@ type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
type ConnectOptions = {
/**
* A browser websocket endpoint to connect to.
*/
wsEndpoint: string;
/**
* Additional HTTP headers to be sent with web socket connect request.
*/
headers?: { [key: string]: string; };
};
/**
* Playwright Test provides many options to configure test environment, [Browser], [BrowserContext] and more.
@ -2735,6 +2746,16 @@ export interface PlaywrightWorkerOptions {
* [testOptions.channel](https://playwright.dev/docs/api/class-testoptions#test-options-channel) take priority over this.
*/
launchOptions: LaunchOptions;
/**
* When connect options are specified, default
* [fixtures.browser](https://playwright.dev/docs/api/class-fixtures#fixtures-browser),
* [fixtures.context](https://playwright.dev/docs/api/class-fixtures#fixtures-context) and
* [fixtures.page](https://playwright.dev/docs/api/class-fixtures#fixtures-page) use the remote browser instead of
* launching a browser locally, and any launch options like
* [testOptions.headless](https://playwright.dev/docs/api/class-testoptions#test-options-headless) or
* [testOptions.channel](https://playwright.dev/docs/api/class-testoptions#test-options-channel) are ignored.
*/
connectOptions: ConnectOptions | undefined;
/**
* Whether to automatically capture a screenshot after each test. Defaults to `'off'`.
* - `'off'`: Do not capture screenshots.

View File

@ -546,3 +546,58 @@ test('should work with video.path() throwing', async ({ runInlineTest }, testInf
const video = fs.readdirSync(dir).find(file => file.endsWith('webm'));
expect(video).toBeTruthy();
});
test('should work with connectOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
globalSetup: './global-setup',
use: {
connectOptions: {
wsEndpoint: process.env.CONNECT_WS_ENDPOINT,
},
},
};
`,
'global-setup.ts': `
module.exports = async () => {
const server = await pwt.chromium.launchServer();
process.env.CONNECT_WS_ENDPOINT = server.wsEndpoint();
return () => server.close();
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ page }) => {
await page.setContent('<div>PASS</div>');
await expect(page.locator('div')).toHaveText('PASS');
});
`,
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should throw with bad connectOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
use: {
connectOptions: {
wsEndpoint: 'http://does-not-exist-bad-domain.oh-no-should-not-work',
},
},
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ page }) => {
await page.setContent('<div>PASS</div>');
await expect(page.locator('div')).toHaveText('PASS');
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('browserType.connect:');
});

View File

@ -297,6 +297,17 @@ type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
type ConnectOptions = {
/**
* A browser websocket endpoint to connect to.
*/
wsEndpoint: string;
/**
* Additional HTTP headers to be sent with web socket connect request.
*/
headers?: { [key: string]: string; };
};
export interface PlaywrightWorkerOptions {
browserName: BrowserName;
@ -304,6 +315,7 @@ export interface PlaywrightWorkerOptions {
headless: boolean | undefined;
channel: BrowserChannel | undefined;
launchOptions: LaunchOptions;
connectOptions: ConnectOptions | undefined;
screenshot: 'off' | 'on' | 'only-on-failure';
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean, screenshots?: boolean, sources?: boolean };
video: VideoMode | /** deprecated */ 'retry-with-video' | { mode: VideoMode, size?: ViewportSize };