playwright/tests/library/multiclient.spec.ts

191 lines
8.5 KiB
TypeScript
Raw Normal View History

/**
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* 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 { expect, playwrightTest } from '../config/browserTest';
import type { Browser, BrowserContext, BrowserServer, ConnectOptions, Page } from 'playwright-core';
type ExtraFixtures = {
remoteServer: BrowserServer;
connect: (wsEndpoint: string, options?: ConnectOptions) => Promise<Browser>,
twoPages: { pageA: Page, pageB: Page },
};
const test = playwrightTest.extend<ExtraFixtures>({
remoteServer: async ({ browserType }, use) => {
const server = await browserType.launchServer({ _sharedBrowser: true } as any);
await use(server);
await server.close();
},
connect: async ({ browserType }, use) => {
let browser: Browser | undefined;
await use(async (wsEndpoint, options = {}) => {
browser = await browserType.connect(wsEndpoint, options);
return browser;
});
await browser?.close();
},
twoPages: async ({ remoteServer, connect }, use) => {
const browserA = await connect(remoteServer.wsEndpoint());
const contextA = await browserA.newContext();
const pageA = await contextA.newPage();
const browserB = await connect(remoteServer.wsEndpoint());
const contextB = browserB.contexts()[0];
const pageB = contextB.pages()[0];
await use({ pageA, pageB });
},
});
test.slow(true, 'All connect tests are slow');
test.skip(({ mode }) => mode.startsWith('service'));
test('should connect two clients', async ({ connect, remoteServer, server }) => {
const browserA = await connect(remoteServer.wsEndpoint());
expect(browserA.contexts().length).toBe(0);
const contextA1 = await browserA.newContext();
const pageA1 = await contextA1.newPage();
await pageA1.goto(server.EMPTY_PAGE);
const browserB = await connect(remoteServer.wsEndpoint());
expect(browserB.contexts().length).toBe(1);
const contextB1 = browserB.contexts()[0];
expect(contextB1.pages().length).toBe(1);
const pageB1 = contextB1.pages()[0];
await expect(pageB1).toHaveURL(server.EMPTY_PAGE);
const contextEventPromise = new Promise<BrowserContext>(f => browserA.on('context', f));
const contextB2 = await browserB.newContext({ baseURL: server.PREFIX });
expect(browserB.contexts()).toEqual([contextB1, contextB2]);
const contextA2 = await contextEventPromise;
expect(browserA.contexts()).toEqual([contextA1, contextA2]);
const pageEventPromise = new Promise<Page>(f => contextB2.on('page', f));
const pageA2 = await contextA2.newPage();
const pageB2 = await pageEventPromise;
await pageA2.goto('/frames/frame.html');
await expect(pageB2).toHaveURL('/frames/frame.html');
// Both contexts and pages should be still operational after any client disconnects.
await browserA.close();
await expect(pageB1).toHaveURL(server.EMPTY_PAGE);
await expect(pageB2).toHaveURL(server.PREFIX + '/frames/frame.html');
});
test('should have separate default timeouts', async ({ twoPages }) => {
const { pageA, pageB } = twoPages;
pageA.setDefaultTimeout(500);
pageB.setDefaultTimeout(600);
const [errorA, errorB] = await Promise.all([
pageA.click('div').catch(e => e),
pageB.click('div').catch(e => e),
]);
expect(errorA.message).toContain('Timeout 500ms exceeded');
expect(errorB.message).toContain('Timeout 600ms exceeded');
});
test('should receive viewport size changes', async ({ twoPages }) => {
const { pageA, pageB } = twoPages;
await pageA.setViewportSize({ width: 567, height: 456 });
expect(pageA.viewportSize()).toEqual({ width: 567, height: 456 });
await expect.poll(() => pageB.viewportSize()).toEqual({ width: 567, height: 456 });
await pageB.setViewportSize({ width: 456, height: 567 });
expect(pageB.viewportSize()).toEqual({ width: 456, height: 567 });
await expect.poll(() => pageA.viewportSize()).toEqual({ width: 456, height: 567 });
});
test('should not allow parallel js coverage', async ({ twoPages, browserName }) => {
test.skip(browserName !== 'chromium');
const { pageA, pageB } = twoPages;
await pageA.coverage.startJSCoverage();
const error = await pageB.coverage.startJSCoverage().catch(e => e);
expect(error.message).toContain('JSCoverage is already enabled');
});
test('should not allow parallel css coverage', async ({ twoPages, browserName }) => {
test.skip(browserName !== 'chromium');
const { pageA, pageB } = twoPages;
await pageA.coverage.startCSSCoverage();
const error = await pageB.coverage.startCSSCoverage().catch(e => e);
expect(error.message).toContain('CSSCoverage is already enabled');
});
test('last emulateMedia wins', async ({ twoPages }) => {
const { pageA, pageB } = twoPages;
await pageA.emulateMedia({ media: 'print' });
expect(await pageB.evaluate(() => window.matchMedia('screen').matches)).toBe(false);
expect(await pageA.evaluate(() => window.matchMedia('print').matches)).toBe(true);
await pageB.emulateMedia({ media: 'screen' });
expect(await pageB.evaluate(() => window.matchMedia('screen').matches)).toBe(true);
expect(await pageA.evaluate(() => window.matchMedia('print').matches)).toBe(false);
});
test('should remove exposed bindings upon disconnect', async ({ twoPages }) => {
const { pageA, pageB } = twoPages;
await pageA.exposeBinding('pageBindingA', () => 'pageBindingAResult');
await pageA.evaluate(() => {
(window as any).pageBindingACopy = (window as any).pageBindingA;
});
expect(await pageB.evaluate(() => (window as any).pageBindingA())).toBe('pageBindingAResult');
expect(await pageB.evaluate(() => !!(window as any).pageBindingACopy)).toBe(true);
await pageA.context().exposeBinding('contextBindingA', () => 'contextBindingAResult');
expect(await pageB.evaluate(() => (window as any).contextBindingA())).toBe('contextBindingAResult');
await pageB.exposeBinding('pageBindingB', () => 'pageBindingBResult');
expect(await pageA.evaluate(() => (window as any).pageBindingB())).toBe('pageBindingBResult');
await pageB.context().exposeBinding('contextBindingB', () => 'contextBindingBResult');
expect(await pageA.evaluate(() => (window as any).contextBindingB())).toBe('contextBindingBResult');
await pageA.context().browser().close();
await new Promise(f => setTimeout(f, 1000)); // Give disconnect some time to cleanup.
expect(await pageB.evaluate(() => (window as any).pageBindingA)).toBe(undefined);
expect(await pageB.evaluate(() => (window as any).contextBindingA)).toBe(undefined);
const error = await pageB.evaluate(() => (window as any).pageBindingACopy()).catch(e => e);
expect(error.message).toContain('binding "pageBindingA" has been removed');
expect(await pageB.evaluate(() => (window as any).pageBindingB())).toBe('pageBindingBResult');
});
test('should remove init scripts upon disconnect', async ({ twoPages, server }) => {
const { pageA, pageB } = twoPages;
await pageA.addInitScript(() => (window as any).pageValueA = 'pageValueA');
await pageA.context().addInitScript(() => (window as any).contextValueA = 'contextValueA');
await pageB.goto(server.EMPTY_PAGE);
expect(await pageB.evaluate(() => (window as any).pageValueA)).toBe('pageValueA');
expect(await pageB.evaluate(() => (window as any).contextValueA)).toBe('contextValueA');
await pageB.addInitScript(() => (window as any).pageValueB = 'pageValueB');
await pageB.context().addInitScript(() => (window as any).contextValueB = 'contextValueB');
await pageA.goto(server.EMPTY_PAGE);
expect(await pageA.evaluate(() => (window as any).pageValueB)).toBe('pageValueB');
expect(await pageA.evaluate(() => (window as any).contextValueB)).toBe('contextValueB');
await pageB.context().browser().close();
await new Promise(f => setTimeout(f, 1000)); // Give disconnect some time to cleanup.
await pageA.goto(server.EMPTY_PAGE);
expect(await pageA.evaluate(() => (window as any).pageValueB)).toBe(undefined);
expect(await pageA.evaluate(() => (window as any).contextValueB)).toBe(undefined);
expect(await pageA.evaluate(() => (window as any).pageValueA)).toBe('pageValueA');
expect(await pageA.evaluate(() => (window as any).contextValueA)).toBe('contextValueA');
});