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(`