diff --git a/tests/browsercontext-proxy.spec.ts b/tests/browsercontext-proxy.spec.ts index 2c0c527042..25120f005c 100644 --- a/tests/browsercontext-proxy.spec.ts +++ b/tests/browsercontext-proxy.spec.ts @@ -15,15 +15,8 @@ */ import { browserTest as it, expect } from './config/browserTest'; -import type { Browser } from '../index'; -let browser: Browser; -it.beforeAll(async ({ browserType, browserOptions }) => { - browser = await browserType.launch({ ...browserOptions, proxy: { server: 'per-context' } }); -}); -it.afterAll(async () => { - await browser.close(); -}); +it.useOptions({ launchOptions: { proxy: { server: 'per-context' } } }); it('should throw for missing global proxy on Chromium Windows', async ({ browserName, platform, browserType, browserOptions, server }) => { 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('Served by the proxy'); }); delete browserOptions.proxy; - const browserWithoutProxyInLaunch = await browserType.launch(browserOptions); - const context = await browserWithoutProxyInLaunch.newContext({ + const browser = await browserType.launch(browserOptions); + const context = await browser.newContext({ ...contextOptions, 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(); await page.goto('http://non-existent.com/target.html'); expect(await page.title()).toBe('Served by the proxy'); - await browserWithoutProxyInLaunch.close(); + await browser.close(); }); -it('should throw for bad server value', async ({ contextOptions }) => { - const error = await browser.newContext({ - ...contextOptions, +it('should throw for bad server value', async ({ contextFactory }) => { + const error = await contextFactory({ // @ts-expect-error server must be a string proxy: { server: 123 } }).catch(e => e); 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) => { res.end('Served by the proxy'); }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}` } }); const page = await context.newPage(); @@ -79,12 +70,11 @@ it('should use proxy', async ({ contextOptions, server }) => { 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) => { res.end('Served by the proxy'); }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}` } }); const page = await context.newPage(); @@ -94,12 +84,11 @@ it('should use proxy twice', async ({ contextOptions, server }) => { 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) => { res.end('Served by the proxy'); }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}` } }); @@ -114,12 +103,11 @@ it('should use proxy for second page', async ({contextOptions, server}) => { 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) => { res.end('Served by the proxy'); }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `127.0.0.1:${server.PORT}` } }); const page = await context.newPage(); @@ -128,23 +116,21 @@ it('should work with IP:PORT notion', async ({contextOptions, server}) => { await context.close(); }); -it('should throw for socks5 authentication', async ({contextOptions}) => { - const error = await browser.newContext({ - ...contextOptions, +it('should throw for socks5 authentication', async ({contextFactory}) => { + const error = await contextFactory({ proxy: { server: `socks5://localhost:1234`, username: 'user', password: 'secret' } }).catch(e => e); expect(error.message).toContain('Browser does not support socks5 proxy authentication'); }); -it('should throw for socks4 authentication', async ({contextOptions}) => { - const error = await browser.newContext({ - ...contextOptions, +it('should throw for socks4 authentication', async ({contextFactory}) => { + const error = await contextFactory({ proxy: { server: `socks4://localhost:1234`, username: 'user', password: 'secret' } }).catch(e => e); 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) => { const auth = req.headers['proxy-authorization']; if (!auth) { @@ -156,8 +142,7 @@ it('should authenticate', async ({contextOptions, server}) => { res.end(`${auth}`); } }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' } }); const page = await context.newPage(); @@ -166,7 +151,7 @@ it('should authenticate', async ({contextOptions, server}) => { 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) => { const auth = req.headers['proxy-authorization']; if (!auth) { @@ -178,8 +163,7 @@ it('should authenticate with empty password', async ({contextOptions, server}) = res.end(`${auth}`); } }); - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}`, username: 'user', password: '' } }); const page = await context.newPage(); @@ -188,8 +172,7 @@ it('should authenticate with empty password', async ({contextOptions, server}) = await context.close(); }); - -it('should isolate proxy credentials between contexts', async ({contextOptions, server, browserName}) => { +it('should isolate proxy credentials between contexts', async ({contextFactory, server, browserName}) => { it.fixme(browserName === 'firefox', 'Credentials from the first context stick around'); 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({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}`, username: 'user1', password: 'secret1' } }); const page = await context.newPage(); @@ -214,8 +196,7 @@ it('should isolate proxy credentials between contexts', async ({contextOptions, await context.close(); } { - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ proxy: { server: `localhost:${server.PORT}`, username: 'user2', password: 'secret2' } }); 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.'); 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. // // @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00 - const context = await browser.newContext({ - ...contextOptions, + const context = await contextFactory({ 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(); }); -it('should use socks proxy', async ({ contextOptions, socksPort }) => { - const context = await browser.newContext({ - ...contextOptions, +it('should use socks proxy', async ({ contextFactory, socksPort }) => { + const context = await contextFactory({ proxy: { server: `socks5://localhost:${socksPort}` } }); const page = await context.newPage(); @@ -278,9 +257,8 @@ it('should use socks proxy', async ({ contextOptions, socksPort }) => { await context.close(); }); -it('should use socks proxy in second page', async ({ contextOptions, socksPort }) => { - const context = await browser.newContext({ - ...contextOptions, +it('should use socks proxy in second page', async ({ contextFactory, socksPort }) => { + const context = await contextFactory({ proxy: { server: `socks5://localhost:${socksPort}` } }); @@ -295,9 +273,8 @@ it('should use socks proxy in second page', async ({ contextOptions, socksPort } await context.close(); }); -it('does launch without a port', async ({ contextOptions }) => { - const context = await browser.newContext({ - ...contextOptions, +it('does launch without a port', async ({ contextFactory }) => { + const context = await contextFactory({ proxy: { server: 'http://localhost' } }); await context.close(); diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 17a53dfcdc..1af05a7c79 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -56,11 +56,11 @@ class PlaywrightEnv { async beforeAll(args: CommonArgs & PlaywrightEnvOptions, workerInfo: folio.WorkerInfo): Promise { this._browserType = args.playwright[args.browserName]; this._browserOptions = { - ...args.launchOptions, _traceDir: args.traceDir, channel: args.browserChannel, headless: !args.headful, handleSIGINT: false, + ...args.launchOptions, } as any; return { browserType: this._browserType, @@ -126,21 +126,30 @@ type BrowserTestArgs = { contextFactory: (options?: BrowserContextOptions) => Promise; }; +type BrowserTestOptions = { + contextOptions?: BrowserContextOptions; +}; + class BrowserEnv { private _browser: Browser | undefined; private _contexts: BrowserContext[] = []; protected _browserVersion: string; + hasBeforeAllOptions(options: BrowserTestOptions) { + return false; + } + async beforeAll(args: PlaywrightWorkerArgs, workerInfo: folio.WorkerInfo) { this._browser = await args.browserType.launch(args.browserOptions); this._browserVersion = this._browser.version(); } - async beforeEach(options: CommonArgs, testInfo: folio.TestInfo): Promise { + async beforeEach(options: CommonArgs & BrowserTestOptions, testInfo: folio.TestInfo): Promise { const debugName = path.relative(testInfo.project.outputDir, testInfo.outputDir).replace(/[\/\\]/g, '-'); const contextOptions = { recordVideo: options.video ? { dir: testInfo.outputPath('') } : undefined, _debugName: debugName, + ...options.contextOptions, } as BrowserContextOptions; testInfo.data.browserVersion = this._browserVersion; diff --git a/tests/snapshotter.spec.ts b/tests/snapshotter.spec.ts index 96c336824d..4be3c54b04 100644 --- a/tests/snapshotter.spec.ts +++ b/tests/snapshotter.spec.ts @@ -14,39 +14,41 @@ * 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 { HttpServer } from '../lib/utils/httpServer'; import { SnapshotServer } from '../lib/server/snapshot/snapshotServer'; -it.describe('snapshots', () => { - let snapshotter: any; - let httpServer: any; - let snapshotPort: number; - - it.skip(({ mode }) => mode !== 'default'); - - it.beforeEach(async ({ toImpl, context }, testInfo) => { - snapshotter = new InMemorySnapshotter(toImpl(context)); +const it = contextTest.extend({ + async beforeEach({ context, toImpl, mode }, testInfo) { + testInfo.skip(mode !== 'default'); + const snapshotter = new InMemorySnapshotter(toImpl(context)); await snapshotter.initialize(); - httpServer = new HttpServer(); - new SnapshotServer(httpServer, snapshotter); - snapshotPort = 11000 + testInfo.workerIndex; - httpServer.start(snapshotPort); - }); + this.httpServer = new HttpServer(); + new SnapshotServer(this.httpServer, snapshotter); + const snapshotPort = 11000 + testInfo.workerIndex; + await this.httpServer.start(snapshotPort); + this.snapshotter = snapshotter; + return { + snapshotter, + snapshotPort, + }; + }, - it.afterEach(async () => { - await snapshotter.dispose(); - httpServer.stop(); - }); + async afterEach() { + await this.snapshotter.dispose(); + await this.httpServer.stop(); + }, +}); - it('should collect snapshot', async ({ page, toImpl }) => { +it.describe('snapshots', () => { + it('should collect snapshot', async ({ page, toImpl, snapshotter }) => { await page.setContent(''); const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot'); expect(distillSnapshot(snapshot)).toBe(''); }); - 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.route('**/style.css', route => { route.fulfill({ body: 'button { color: red; }', }).catch(() => {}); @@ -58,7 +60,7 @@ it.describe('snapshots', () => { expect(resources[cssHref]).toBeTruthy(); }); - it('should collect multiple', async ({ page, toImpl }) => { + it('should collect multiple', async ({ page, toImpl, snapshotter }) => { await page.setContent(''); const snapshots = []; snapshotter.on('snapshot', snapshot => snapshots.push(snapshot)); @@ -67,7 +69,7 @@ it.describe('snapshots', () => { 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(''); const snapshot1 = await snapshotter.captureSnapshot(toImpl(page), 'snapshot1'); expect(distillSnapshot(snapshot1)).toBe(''); @@ -77,7 +79,7 @@ it.describe('snapshots', () => { expect(distillSnapshot(snapshot2)).toBe(''); }); - 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.route('**/style.css', route => { route.fulfill({ body: 'button { color: red; }', }).catch(() => {}); @@ -95,7 +97,7 @@ it.describe('snapshots', () => { 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'); await page.route('**/empty.html', route => { @@ -136,7 +138,7 @@ it.describe('snapshots', () => { 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(''); { 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(''); { const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot'); diff --git a/tests/tap.spec.ts b/tests/tap.spec.ts index 8ae9c39190..5ffc97c773 100644 --- a/tests/tap.spec.ts +++ b/tests/tap.spec.ts @@ -14,19 +14,11 @@ * limitations under the License. */ -import { browserTest, expect } from './config/browserTest'; +import { contextTest as it, expect } from './config/browserTest'; import { ElementHandle } from '../index'; import type { ServerResponse } from 'http'; -const it = browserTest.extend({ - async beforeEach({ browser }) { - this.page = await browser.newPage({ hasTouch: true }); - return { page: this.page }; - }, - async afterEach() { - await this.page.close(); - } -}); +it.useOptions({ contextOptions: { hasTouch: true } }); it('should send all of the correct events', async ({ page }) => { await page.setContent(`