test: simplify more tests (#6471)

This commit is contained in:
Dmitry Gozman 2021-05-09 17:47:20 -07:00 committed by GitHub
parent a5143ebaa9
commit 76e409637a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 95 deletions

View file

@ -15,15 +15,8 @@
*/ */
import { browserTest as it, expect } from './config/browserTest'; import { browserTest as it, expect } from './config/browserTest';
import type { Browser } from '../index';
let browser: Browser; it.useOptions({ launchOptions: { proxy: { server: 'per-context' } } });
it.beforeAll(async ({ browserType, browserOptions }) => {
browser = await browserType.launch({ ...browserOptions, proxy: { server: 'per-context' } });
});
it.afterAll(async () => {
await browser.close();
});
it('should throw for missing global proxy on Chromium Windows', async ({ browserName, platform, browserType, browserOptions, server }) => { it('should throw for missing global proxy on Chromium Windows', async ({ browserName, platform, browserType, browserOptions, server }) => {
it.skip(browserName !== 'chromium' || platform !== 'win32'); it.skip(browserName !== 'chromium' || platform !== 'win32');
@ -44,8 +37,8 @@ it('should work when passing the proxy only on the context level', async ({brows
res.end('<html><title>Served by the proxy</title></html>'); res.end('<html><title>Served by the proxy</title></html>');
}); });
delete browserOptions.proxy; delete browserOptions.proxy;
const browserWithoutProxyInLaunch = await browserType.launch(browserOptions); const browser = await browserType.launch(browserOptions);
const context = await browserWithoutProxyInLaunch.newContext({ const context = await browser.newContext({
...contextOptions, ...contextOptions,
proxy: { server: `localhost:${server.PORT}` } proxy: { server: `localhost:${server.PORT}` }
}); });
@ -53,24 +46,22 @@ it('should work when passing the proxy only on the context level', async ({brows
const page = await context.newPage(); const page = await context.newPage();
await page.goto('http://non-existent.com/target.html'); await page.goto('http://non-existent.com/target.html');
expect(await page.title()).toBe('Served by the proxy'); expect(await page.title()).toBe('Served by the proxy');
await browserWithoutProxyInLaunch.close(); await browser.close();
}); });
it('should throw for bad server value', async ({ contextOptions }) => { it('should throw for bad server value', async ({ contextFactory }) => {
const error = await browser.newContext({ const error = await contextFactory({
...contextOptions,
// @ts-expect-error server must be a string // @ts-expect-error server must be a string
proxy: { server: 123 } proxy: { server: 123 }
}).catch(e => e); }).catch(e => e);
expect(error.message).toContain('proxy.server: expected string, got number'); expect(error.message).toContain('proxy.server: expected string, got number');
}); });
it('should use proxy', async ({ contextOptions, server }) => { it('should use proxy', async ({ contextFactory, server }) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>'); res.end('<html><title>Served by the proxy</title></html>');
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}` } proxy: { server: `localhost:${server.PORT}` }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -79,12 +70,11 @@ it('should use proxy', async ({ contextOptions, server }) => {
await context.close(); await context.close();
}); });
it('should use proxy twice', async ({ contextOptions, server }) => { it('should use proxy twice', async ({ contextFactory, server }) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>'); res.end('<html><title>Served by the proxy</title></html>');
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}` } proxy: { server: `localhost:${server.PORT}` }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -94,12 +84,11 @@ it('should use proxy twice', async ({ contextOptions, server }) => {
await context.close(); await context.close();
}); });
it('should use proxy for second page', async ({contextOptions, server}) => { it('should use proxy for second page', async ({contextFactory, server}) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>'); res.end('<html><title>Served by the proxy</title></html>');
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}` } proxy: { server: `localhost:${server.PORT}` }
}); });
@ -114,12 +103,11 @@ it('should use proxy for second page', async ({contextOptions, server}) => {
await context.close(); await context.close();
}); });
it('should work with IP:PORT notion', async ({contextOptions, server}) => { it('should work with IP:PORT notion', async ({contextFactory, server}) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>'); res.end('<html><title>Served by the proxy</title></html>');
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `127.0.0.1:${server.PORT}` } proxy: { server: `127.0.0.1:${server.PORT}` }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -128,23 +116,21 @@ it('should work with IP:PORT notion', async ({contextOptions, server}) => {
await context.close(); await context.close();
}); });
it('should throw for socks5 authentication', async ({contextOptions}) => { it('should throw for socks5 authentication', async ({contextFactory}) => {
const error = await browser.newContext({ const error = await contextFactory({
...contextOptions,
proxy: { server: `socks5://localhost:1234`, username: 'user', password: 'secret' } proxy: { server: `socks5://localhost:1234`, username: 'user', password: 'secret' }
}).catch(e => e); }).catch(e => e);
expect(error.message).toContain('Browser does not support socks5 proxy authentication'); expect(error.message).toContain('Browser does not support socks5 proxy authentication');
}); });
it('should throw for socks4 authentication', async ({contextOptions}) => { it('should throw for socks4 authentication', async ({contextFactory}) => {
const error = await browser.newContext({ const error = await contextFactory({
...contextOptions,
proxy: { server: `socks4://localhost:1234`, username: 'user', password: 'secret' } proxy: { server: `socks4://localhost:1234`, username: 'user', password: 'secret' }
}).catch(e => e); }).catch(e => e);
expect(error.message).toContain('Socks4 proxy protocol does not support authentication'); expect(error.message).toContain('Socks4 proxy protocol does not support authentication');
}); });
it('should authenticate', async ({contextOptions, server}) => { it('should authenticate', async ({contextFactory, server}) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
const auth = req.headers['proxy-authorization']; const auth = req.headers['proxy-authorization'];
if (!auth) { if (!auth) {
@ -156,8 +142,7 @@ it('should authenticate', async ({contextOptions, server}) => {
res.end(`<html><title>${auth}</title></html>`); res.end(`<html><title>${auth}</title></html>`);
} }
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' } proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -166,7 +151,7 @@ it('should authenticate', async ({contextOptions, server}) => {
await context.close(); await context.close();
}); });
it('should authenticate with empty password', async ({contextOptions, server}) => { it('should authenticate with empty password', async ({contextFactory, server}) => {
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
const auth = req.headers['proxy-authorization']; const auth = req.headers['proxy-authorization'];
if (!auth) { if (!auth) {
@ -178,8 +163,7 @@ it('should authenticate with empty password', async ({contextOptions, server}) =
res.end(`<html><title>${auth}</title></html>`); res.end(`<html><title>${auth}</title></html>`);
} }
}); });
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}`, username: 'user', password: '' } proxy: { server: `localhost:${server.PORT}`, username: 'user', password: '' }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -188,8 +172,7 @@ it('should authenticate with empty password', async ({contextOptions, server}) =
await context.close(); await context.close();
}); });
it('should isolate proxy credentials between contexts', async ({contextFactory, server, browserName}) => {
it('should isolate proxy credentials between contexts', async ({contextOptions, server, browserName}) => {
it.fixme(browserName === 'firefox', 'Credentials from the first context stick around'); it.fixme(browserName === 'firefox', 'Credentials from the first context stick around');
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
@ -204,8 +187,7 @@ it('should isolate proxy credentials between contexts', async ({contextOptions,
} }
}); });
{ {
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}`, username: 'user1', password: 'secret1' } proxy: { server: `localhost:${server.PORT}`, username: 'user1', password: 'secret1' }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -214,8 +196,7 @@ it('should isolate proxy credentials between contexts', async ({contextOptions,
await context.close(); await context.close();
} }
{ {
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}`, username: 'user2', password: 'secret2' } proxy: { server: `localhost:${server.PORT}`, username: 'user2', password: 'secret2' }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -225,7 +206,7 @@ it('should isolate proxy credentials between contexts', async ({contextOptions,
} }
}); });
it('should exclude patterns', async ({contextOptions, server, browserName, headful}) => { it('should exclude patterns', async ({contextFactory, server, browserName, headful}) => {
it.fixme(browserName === 'chromium' && headful, 'Chromium headful crashes with CHECK(!in_frame_tree_) in RenderFrameImpl::OnDeleteFrame.'); it.fixme(browserName === 'chromium' && headful, 'Chromium headful crashes with CHECK(!in_frame_tree_) in RenderFrameImpl::OnDeleteFrame.');
server.setRoute('/target.html', async (req, res) => { server.setRoute('/target.html', async (req, res) => {
@ -235,8 +216,7 @@ it('should exclude patterns', async ({contextOptions, server, browserName, headf
// that resolves everything to some weird search results page. // that resolves everything to some weird search results page.
// //
// @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00 // @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `localhost:${server.PORT}`, bypass: '1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test' } proxy: { server: `localhost:${server.PORT}`, bypass: '1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test' }
}); });
@ -267,9 +247,8 @@ it('should exclude patterns', async ({contextOptions, server, browserName, headf
await context.close(); await context.close();
}); });
it('should use socks proxy', async ({ contextOptions, socksPort }) => { it('should use socks proxy', async ({ contextFactory, socksPort }) => {
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `socks5://localhost:${socksPort}` } proxy: { server: `socks5://localhost:${socksPort}` }
}); });
const page = await context.newPage(); const page = await context.newPage();
@ -278,9 +257,8 @@ it('should use socks proxy', async ({ contextOptions, socksPort }) => {
await context.close(); await context.close();
}); });
it('should use socks proxy in second page', async ({ contextOptions, socksPort }) => { it('should use socks proxy in second page', async ({ contextFactory, socksPort }) => {
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: `socks5://localhost:${socksPort}` } proxy: { server: `socks5://localhost:${socksPort}` }
}); });
@ -295,9 +273,8 @@ it('should use socks proxy in second page', async ({ contextOptions, socksPort }
await context.close(); await context.close();
}); });
it('does launch without a port', async ({ contextOptions }) => { it('does launch without a port', async ({ contextFactory }) => {
const context = await browser.newContext({ const context = await contextFactory({
...contextOptions,
proxy: { server: 'http://localhost' } proxy: { server: 'http://localhost' }
}); });
await context.close(); await context.close();

View file

@ -56,11 +56,11 @@ class PlaywrightEnv {
async beforeAll(args: CommonArgs & PlaywrightEnvOptions, workerInfo: folio.WorkerInfo): Promise<PlaywrightWorkerArgs> { async beforeAll(args: CommonArgs & PlaywrightEnvOptions, workerInfo: folio.WorkerInfo): Promise<PlaywrightWorkerArgs> {
this._browserType = args.playwright[args.browserName]; this._browserType = args.playwright[args.browserName];
this._browserOptions = { this._browserOptions = {
...args.launchOptions,
_traceDir: args.traceDir, _traceDir: args.traceDir,
channel: args.browserChannel, channel: args.browserChannel,
headless: !args.headful, headless: !args.headful,
handleSIGINT: false, handleSIGINT: false,
...args.launchOptions,
} as any; } as any;
return { return {
browserType: this._browserType, browserType: this._browserType,
@ -126,21 +126,30 @@ type BrowserTestArgs = {
contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>; contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
}; };
type BrowserTestOptions = {
contextOptions?: BrowserContextOptions;
};
class BrowserEnv { class BrowserEnv {
private _browser: Browser | undefined; private _browser: Browser | undefined;
private _contexts: BrowserContext[] = []; private _contexts: BrowserContext[] = [];
protected _browserVersion: string; protected _browserVersion: string;
hasBeforeAllOptions(options: BrowserTestOptions) {
return false;
}
async beforeAll(args: PlaywrightWorkerArgs, workerInfo: folio.WorkerInfo) { async beforeAll(args: PlaywrightWorkerArgs, workerInfo: folio.WorkerInfo) {
this._browser = await args.browserType.launch(args.browserOptions); this._browser = await args.browserType.launch(args.browserOptions);
this._browserVersion = this._browser.version(); this._browserVersion = this._browser.version();
} }
async beforeEach(options: CommonArgs, testInfo: folio.TestInfo): Promise<BrowserTestArgs> { async beforeEach(options: CommonArgs & BrowserTestOptions, testInfo: folio.TestInfo): Promise<BrowserTestArgs> {
const debugName = path.relative(testInfo.project.outputDir, testInfo.outputDir).replace(/[\/\\]/g, '-'); const debugName = path.relative(testInfo.project.outputDir, testInfo.outputDir).replace(/[\/\\]/g, '-');
const contextOptions = { const contextOptions = {
recordVideo: options.video ? { dir: testInfo.outputPath('') } : undefined, recordVideo: options.video ? { dir: testInfo.outputPath('') } : undefined,
_debugName: debugName, _debugName: debugName,
...options.contextOptions,
} as BrowserContextOptions; } as BrowserContextOptions;
testInfo.data.browserVersion = this._browserVersion; testInfo.data.browserVersion = this._browserVersion;

View file

@ -14,39 +14,41 @@
* limitations under the License. * limitations under the License.
*/ */
import { contextTest as it, expect } from './config/browserTest'; import { contextTest, expect } from './config/browserTest';
import { InMemorySnapshotter } from '../lib/server/snapshot/inMemorySnapshotter'; import { InMemorySnapshotter } from '../lib/server/snapshot/inMemorySnapshotter';
import { HttpServer } from '../lib/utils/httpServer'; import { HttpServer } from '../lib/utils/httpServer';
import { SnapshotServer } from '../lib/server/snapshot/snapshotServer'; import { SnapshotServer } from '../lib/server/snapshot/snapshotServer';
it.describe('snapshots', () => { const it = contextTest.extend({
let snapshotter: any; async beforeEach({ context, toImpl, mode }, testInfo) {
let httpServer: any; testInfo.skip(mode !== 'default');
let snapshotPort: number; const snapshotter = new InMemorySnapshotter(toImpl(context));
it.skip(({ mode }) => mode !== 'default');
it.beforeEach(async ({ toImpl, context }, testInfo) => {
snapshotter = new InMemorySnapshotter(toImpl(context));
await snapshotter.initialize(); await snapshotter.initialize();
httpServer = new HttpServer(); this.httpServer = new HttpServer();
new SnapshotServer(httpServer, snapshotter); new SnapshotServer(this.httpServer, snapshotter);
snapshotPort = 11000 + testInfo.workerIndex; const snapshotPort = 11000 + testInfo.workerIndex;
httpServer.start(snapshotPort); await this.httpServer.start(snapshotPort);
this.snapshotter = snapshotter;
return {
snapshotter,
snapshotPort,
};
},
async afterEach() {
await this.snapshotter.dispose();
await this.httpServer.stop();
},
}); });
it.afterEach(async () => { it.describe('snapshots', () => {
await snapshotter.dispose(); it('should collect snapshot', async ({ page, toImpl, snapshotter }) => {
httpServer.stop();
});
it('should collect snapshot', async ({ page, toImpl }) => {
await page.setContent('<button>Hello</button>'); await page.setContent('<button>Hello</button>');
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot'); const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot');
expect(distillSnapshot(snapshot)).toBe('<BUTTON>Hello</BUTTON>'); expect(distillSnapshot(snapshot)).toBe('<BUTTON>Hello</BUTTON>');
}); });
it('should capture resources', async ({ page, toImpl, server }) => { it('should capture resources', async ({ page, toImpl, server, snapshotter }) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/style.css', route => { await page.route('**/style.css', route => {
route.fulfill({ body: 'button { color: red; }', }).catch(() => {}); route.fulfill({ body: 'button { color: red; }', }).catch(() => {});
@ -58,7 +60,7 @@ it.describe('snapshots', () => {
expect(resources[cssHref]).toBeTruthy(); expect(resources[cssHref]).toBeTruthy();
}); });
it('should collect multiple', async ({ page, toImpl }) => { it('should collect multiple', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<button>Hello</button>'); await page.setContent('<button>Hello</button>');
const snapshots = []; const snapshots = [];
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot)); snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
@ -67,7 +69,7 @@ it.describe('snapshots', () => {
expect(snapshots.length).toBe(2); expect(snapshots.length).toBe(2);
}); });
it('should respect inline CSSOM change', async ({ page, toImpl }) => { it('should respect inline CSSOM change', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<style>button { color: red; }</style><button>Hello</button>'); await page.setContent('<style>button { color: red; }</style><button>Hello</button>');
const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1'); const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
expect(distillSnapshot(snapshot1)).toBe('<style>button { color: red; }</style><BUTTON>Hello</BUTTON>'); expect(distillSnapshot(snapshot1)).toBe('<style>button { color: red; }</style><BUTTON>Hello</BUTTON>');
@ -77,7 +79,7 @@ it.describe('snapshots', () => {
expect(distillSnapshot(snapshot2)).toBe('<style>button { color: blue; }</style><BUTTON>Hello</BUTTON>'); expect(distillSnapshot(snapshot2)).toBe('<style>button { color: blue; }</style><BUTTON>Hello</BUTTON>');
}); });
it('should respect subresource CSSOM change', async ({ page, server, toImpl }) => { it('should respect subresource CSSOM change', async ({ page, server, toImpl, snapshotter }) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.route('**/style.css', route => { await page.route('**/style.css', route => {
route.fulfill({ body: 'button { color: red; }', }).catch(() => {}); route.fulfill({ body: 'button { color: red; }', }).catch(() => {});
@ -95,7 +97,7 @@ it.describe('snapshots', () => {
expect(snapshotter.resourceContent(sha1).toString()).toBe('button { color: blue; }'); expect(snapshotter.resourceContent(sha1).toString()).toBe('button { color: blue; }');
}); });
it('should capture iframe', async ({ page, contextFactory, server, toImpl, browserName }) => { it('should capture iframe', async ({ page, contextFactory, server, toImpl, browserName, snapshotter, snapshotPort }) => {
it.skip(browserName === 'firefox'); it.skip(browserName === 'firefox');
await page.route('**/empty.html', route => { await page.route('**/empty.html', route => {
@ -136,7 +138,7 @@ it.describe('snapshots', () => {
expect(await button.textContent()).toBe('Hello iframe'); expect(await button.textContent()).toBe('Hello iframe');
}); });
it('should capture snapshot target', async ({ page, toImpl }) => { it('should capture snapshot target', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<button>Hello</button><button>World</button>'); await page.setContent('<button>Hello</button><button>World</button>');
{ {
const handle = await page.$('text=Hello'); const handle = await page.$('text=Hello');
@ -150,7 +152,7 @@ it.describe('snapshots', () => {
} }
}); });
it('should collect on attribute change', async ({ page, toImpl }) => { it('should collect on attribute change', async ({ page, toImpl, snapshotter }) => {
await page.setContent('<button>Hello</button>'); await page.setContent('<button>Hello</button>');
{ {
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot'); const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot');

View file

@ -14,19 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { browserTest, expect } from './config/browserTest'; import { contextTest as it, expect } from './config/browserTest';
import { ElementHandle } from '../index'; import { ElementHandle } from '../index';
import type { ServerResponse } from 'http'; import type { ServerResponse } from 'http';
const it = browserTest.extend({ it.useOptions({ contextOptions: { hasTouch: true } });
async beforeEach({ browser }) {
this.page = await browser.newPage({ hasTouch: true });
return { page: this.page };
},
async afterEach() {
await this.page.close();
}
});
it('should send all of the correct events', async ({ page }) => { it('should send all of the correct events', async ({ page }) => {
await page.setContent(` await page.setContent(`