diff --git a/test/browsertype-connect-subprocess.spec.ts b/test/browsertype-connect-subprocess.spec.ts
new file mode 100644
index 0000000000000..3b6ff7f3d49ad
--- /dev/null
+++ b/test/browsertype-connect-subprocess.spec.ts
@@ -0,0 +1,46 @@
+/**
+ * Copyright 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 { options } from './playwright.fixtures';
+import './remoteServer.fixture';
+import utils from './utils';
+
+it.skip(options.WIRE).slow()('should connect to server from another process', async({ browserType, remoteServer }) => {
+ const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
+ const page = await browser.newPage();
+ expect(await page.evaluate('2 + 3')).toBe(5);
+ await browser.close();
+});
+
+it.skip(options.WIRE).fail(true).slow()('should respect selectors in another process', async({ playwright, browserType, remoteServer }) => {
+ const mycss = () => ({
+ create(root, target) {},
+ query(root, selector) {
+ return root.querySelector(selector);
+ },
+ queryAll(root: HTMLElement, selector: string) {
+ return Array.from(root.querySelectorAll(selector));
+ }
+ });
+ await utils.registerEngine(playwright, 'mycss', mycss);
+
+ const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
+ const page = await browser.newPage();
+ await page.setContent(`
hello
`);
+ expect(await page.innerHTML('css=div')).toBe('hello');
+ expect(await page.innerHTML('mycss=div')).toBe('hello');
+ await browser.close();
+});
diff --git a/test/browsertype-connect.spec.ts b/test/browsertype-connect.spec.ts
index 030c4eb02c13c..6423a028be45d 100644
--- a/test/browsertype-connect.spec.ts
+++ b/test/browsertype-connect.spec.ts
@@ -16,6 +16,7 @@
*/
import { options } from './playwright.fixtures';
+import utils from './utils';
it.skip(options.WIRE).slow()('should be able to reconnect to a browser', async({browserType, defaultBrowserOptions, server}) => {
const browserServer = await browserType.launchServer(defaultBrowserOptions);
@@ -65,3 +66,24 @@ it.skip(options.WIRE)('should throw when used after isConnected returns false',
const error = await page.evaluate('1 + 1').catch(e => e) as Error;
expect(error.message).toContain('has been closed');
});
+
+it.skip(options.WIRE)('should respect selectors', async({playwright, browserType, defaultBrowserOptions}) => {
+ const mycss = () => ({
+ create(root, target) {},
+ query(root, selector) {
+ return root.querySelector(selector);
+ },
+ queryAll(root: HTMLElement, selector: string) {
+ return Array.from(root.querySelectorAll(selector));
+ }
+ });
+ await utils.registerEngine(playwright, 'mycss', mycss);
+
+ const browserServer = await browserType.launchServer(defaultBrowserOptions);
+ const browser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ const page = await browser.newPage();
+ await page.setContent(`hello
`);
+ expect(await page.innerHTML('css=div')).toBe('hello');
+ expect(await page.innerHTML('mycss=div')).toBe('hello');
+ await browserServer.close();
+});
diff --git a/test/fixtures.spec.ts b/test/fixtures.spec.ts
index 6b0c9d520b928..8c4bb6f7b084a 100644
--- a/test/fixtures.spec.ts
+++ b/test/fixtures.spec.ts
@@ -15,190 +15,93 @@
* limitations under the License.
*/
import { options } from './playwright.fixtures';
-import { registerFixture } from '../test-runner';
+import './remoteServer.fixture';
+import { execSync } from 'child_process';
import path from 'path';
-import {spawn, execSync} from 'child_process';
-import { BrowserType, Browser, LaunchOptions } from '..';
-const playwrightPath = path.join(__dirname, '..');
-
-class Wrapper {
- _output: Map;
- _outputCallback: Map;
- _browserType: BrowserType;
- _child: import("child_process").ChildProcess;
- _exitPromise: Promise;
- _exitAndDisconnectPromise: Promise;
- constructor(browserType: BrowserType, defaultBrowserOptions: LaunchOptions, extraOptions?: { stallOnClose: boolean; }) {
- this._output = new Map();
- this._outputCallback = new Map();
-
- this._browserType = browserType;
- const launchOptions = {...defaultBrowserOptions,
- handleSIGINT: true,
- handleSIGTERM: true,
- handleSIGHUP: true,
- executablePath: defaultBrowserOptions.executablePath || browserType.executablePath(),
- logger: undefined,
- };
- const options = {
- playwrightPath,
- browserTypeName: browserType.name(),
- launchOptions,
- ...extraOptions,
- };
- this._child = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), JSON.stringify(options)]);
- this._child.on('error', (...args) => console.log("ERROR", ...args));
- this._exitPromise = new Promise(resolve => this._child.on('exit', resolve));
-
- let outputString = '';
- this._child.stdout.on('data', data => {
- outputString += data.toString();
- // Uncomment to debug.
- // console.log(data.toString());
- let match;
- while (match = outputString.match(/\(([^()]+)=>([^()]+)\)/)) {
- const key = match[1];
- const value = match[2];
- this._addOutput(key, value);
- outputString = outputString.substring(match.index + match[0].length);
- }
- });
- }
-
- _addOutput(key, value) {
- this._output.set(key, value);
- const cb = this._outputCallback.get(key);
- this._outputCallback.delete(key);
- if (cb)
- cb();
- }
-
- async out(key) {
- if (!this._output.has(key))
- await new Promise(f => this._outputCallback.set(key, f));
- return this._output.get(key);
- }
-
- async connect() {
- const wsEndpoint = await this.out('wsEndpoint');
- const browser = await this._browserType.connect({ wsEndpoint });
- this._exitAndDisconnectPromise = Promise.all([
- this._exitPromise,
- new Promise(resolve => browser.once('disconnected', resolve)),
- ]).then(([exitCode]) => exitCode);
- }
-
- child() {
- return this._child;
- }
-
- async childExitCode() {
- return await this._exitAndDisconnectPromise;
- }
-}
-
-declare global {
- interface TestState {
- wrapper: Wrapper;
- stallingWrapper: Wrapper;
- }
-}
-registerFixture('wrapper', async ({browserType, defaultBrowserOptions}, test) => {
- const wrapper = new Wrapper(browserType, defaultBrowserOptions);
- await wrapper.connect();
- await test(wrapper);
-});
-
-registerFixture('stallingWrapper', async ({browserType, defaultBrowserOptions}, test) => {
- const wrapper = new Wrapper(browserType, defaultBrowserOptions, { stallOnClose: true });
- await wrapper.connect();
- await test(wrapper);
-});
-
-it.slow()('should close the browser when the node process closes', async ({wrapper}) => {
+it.slow()('should close the browser when the node process closes', async ({remoteServer}) => {
if (WIN)
- execSync(`taskkill /pid ${wrapper.child().pid} /T /F`);
+ execSync(`taskkill /pid ${remoteServer.child().pid} /T /F`);
else
- process.kill(wrapper.child().pid);
- expect(await wrapper.childExitCode()).toBe(WIN ? 1 : 0);
+ process.kill(remoteServer.child().pid);
+ expect(await remoteServer.childExitCode()).toBe(WIN ? 1 : 0);
// We might not get browser exitCode in time when killing the parent node process,
// so we don't check it here.
});
// Cannot reliably send signals on Windows.
-it.skip(WIN || !options.HEADLESS).slow()('should report browser close signal', async ({wrapper}) => {
- const pid = await wrapper.out('pid');
+it.skip(WIN || !options.HEADLESS).slow()('should report browser close signal', async ({remoteServer}) => {
+ const pid = await remoteServer.out('pid');
process.kill(-pid, 'SIGTERM');
- expect(await wrapper.out('exitCode')).toBe('null');
- expect(await wrapper.out('signal')).toBe('SIGTERM');
- process.kill(wrapper.child().pid);
- await wrapper.childExitCode();
+ expect(await remoteServer.out('exitCode')).toBe('null');
+ expect(await remoteServer.out('signal')).toBe('SIGTERM');
+ process.kill(remoteServer.child().pid);
+ await remoteServer.childExitCode();
});
-it.skip(WIN || !options.HEADLESS).slow()('should report browser close signal 2', async ({wrapper}) => {
- const pid = await wrapper.out('pid');
+it.skip(WIN || !options.HEADLESS).slow()('should report browser close signal 2', async ({remoteServer}) => {
+ const pid = await remoteServer.out('pid');
process.kill(-pid, 'SIGKILL');
- expect(await wrapper.out('exitCode')).toBe('null');
- expect(await wrapper.out('signal')).toBe('SIGKILL');
- process.kill(wrapper.child().pid);
- await wrapper.childExitCode();
+ expect(await remoteServer.out('exitCode')).toBe('null');
+ expect(await remoteServer.out('signal')).toBe('SIGKILL');
+ process.kill(remoteServer.child().pid);
+ await remoteServer.childExitCode();
});
-it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGINT', async ({wrapper}) => {
- process.kill(wrapper.child().pid, 'SIGINT');
- expect(await wrapper.out('exitCode')).toBe('0');
- expect(await wrapper.out('signal')).toBe('null');
- expect(await wrapper.childExitCode()).toBe(130);
+it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGINT', async ({remoteServer}) => {
+ process.kill(remoteServer.child().pid, 'SIGINT');
+ expect(await remoteServer.out('exitCode')).toBe('0');
+ expect(await remoteServer.out('signal')).toBe('null');
+ expect(await remoteServer.childExitCode()).toBe(130);
});
-it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGTERM', async ({wrapper}) => {
- process.kill(wrapper.child().pid, 'SIGTERM');
- expect(await wrapper.out('exitCode')).toBe('0');
- expect(await wrapper.out('signal')).toBe('null');
- expect(await wrapper.childExitCode()).toBe(0);
+it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGTERM', async ({remoteServer}) => {
+ process.kill(remoteServer.child().pid, 'SIGTERM');
+ expect(await remoteServer.out('exitCode')).toBe('0');
+ expect(await remoteServer.out('signal')).toBe('null');
+ expect(await remoteServer.childExitCode()).toBe(0);
});
-it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGHUP', async ({wrapper}) => {
- process.kill(wrapper.child().pid, 'SIGHUP');
- expect(await wrapper.out('exitCode')).toBe('0');
- expect(await wrapper.out('signal')).toBe('null');
- expect(await wrapper.childExitCode()).toBe(0);
+it.skip(WIN || !options.HEADLESS).slow()('should close the browser on SIGHUP', async ({remoteServer}) => {
+ process.kill(remoteServer.child().pid, 'SIGHUP');
+ expect(await remoteServer.out('exitCode')).toBe('0');
+ expect(await remoteServer.out('signal')).toBe('null');
+ expect(await remoteServer.childExitCode()).toBe(0);
});
-it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on double SIGINT', async ({stallingWrapper}) => {
- const wrapper = stallingWrapper;
- process.kill(wrapper.child().pid, 'SIGINT');
- await wrapper.out('stalled');
- process.kill(wrapper.child().pid, 'SIGINT');
- expect(await wrapper.out('exitCode')).toBe('null');
- expect(await wrapper.out('signal')).toBe('SIGKILL');
- expect(await wrapper.childExitCode()).toBe(130);
+it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on double SIGINT', async ({stallingRemoteServer}) => {
+ const remoteServer = stallingRemoteServer;
+ process.kill(remoteServer.child().pid, 'SIGINT');
+ await remoteServer.out('stalled');
+ process.kill(remoteServer.child().pid, 'SIGINT');
+ expect(await remoteServer.out('exitCode')).toBe('null');
+ expect(await remoteServer.out('signal')).toBe('SIGKILL');
+ expect(await remoteServer.childExitCode()).toBe(130);
});
-it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on SIGINT + SIGTERM', async ({stallingWrapper}) => {
- const wrapper = stallingWrapper;
- process.kill(wrapper.child().pid, 'SIGINT');
- await wrapper.out('stalled');
- process.kill(wrapper.child().pid, 'SIGTERM');
- expect(await wrapper.out('exitCode')).toBe('null');
- expect(await wrapper.out('signal')).toBe('SIGKILL');
- expect(await wrapper.childExitCode()).toBe(0);
+it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on SIGINT + SIGTERM', async ({stallingRemoteServer}) => {
+ const remoteServer = stallingRemoteServer;
+ process.kill(remoteServer.child().pid, 'SIGINT');
+ await remoteServer.out('stalled');
+ process.kill(remoteServer.child().pid, 'SIGTERM');
+ expect(await remoteServer.out('exitCode')).toBe('null');
+ expect(await remoteServer.out('signal')).toBe('SIGKILL');
+ expect(await remoteServer.childExitCode()).toBe(0);
});
-it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on SIGTERM + SIGINT', async ({stallingWrapper}) => {
- const wrapper = stallingWrapper;
- process.kill(wrapper.child().pid, 'SIGTERM');
- await wrapper.out('stalled');
- process.kill(wrapper.child().pid, 'SIGINT');
- expect(await wrapper.out('exitCode')).toBe('null');
- expect(await wrapper.out('signal')).toBe('SIGKILL');
- expect(await wrapper.childExitCode()).toBe(130);
+it.skip(WIN || !options.HEADLESS).slow()('should kill the browser on SIGTERM + SIGINT', async ({stallingRemoteServer}) => {
+ const remoteServer = stallingRemoteServer;
+ process.kill(remoteServer.child().pid, 'SIGTERM');
+ await remoteServer.out('stalled');
+ process.kill(remoteServer.child().pid, 'SIGINT');
+ expect(await remoteServer.out('exitCode')).toBe('null');
+ expect(await remoteServer.out('signal')).toBe('SIGKILL');
+ expect(await remoteServer.childExitCode()).toBe(130);
});
it('caller file path', async ({}) => {
- const stackTrace = require(path.join(playwrightPath, 'lib', 'utils', 'stackTrace'));
+ const stackTrace = require(path.join(__dirname, '..', 'lib', 'utils', 'stackTrace'));
const callme = require('./fixtures/callback');
const filePath = callme(() => {
return stackTrace.getCallerFilePath(path.join(__dirname, 'fixtures') + path.sep);
diff --git a/test/remoteServer.fixture.ts b/test/remoteServer.fixture.ts
new file mode 100644
index 0000000000000..d4b5868834b08
--- /dev/null
+++ b/test/remoteServer.fixture.ts
@@ -0,0 +1,131 @@
+/**
+ * Copyright 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 { registerFixture } from '../test-runner/lib';
+
+import path from 'path';
+import { spawn } from 'child_process';
+import { BrowserType, Browser, LaunchOptions } from '..';
+
+declare global {
+ interface TestState {
+ remoteServer: RemoteServer;
+ stallingRemoteServer: RemoteServer;
+ }
+}
+
+const playwrightPath = path.join(__dirname, '..');
+
+class RemoteServer {
+ _output: Map;
+ _outputCallback: Map;
+ _browserType: BrowserType;
+ _child: import("child_process").ChildProcess;
+ _exitPromise: Promise;
+ _exitAndDisconnectPromise: Promise;
+ _browser: Browser;
+ _didExit: boolean;
+ _wsEndpoint: string;
+
+ async _start(browserType: BrowserType, defaultBrowserOptions: LaunchOptions, extraOptions?: { stallOnClose: boolean; }) {
+ this._output = new Map();
+ this._outputCallback = new Map();
+ this._didExit = false;
+
+ this._browserType = browserType;
+ const launchOptions = {...defaultBrowserOptions,
+ handleSIGINT: true,
+ handleSIGTERM: true,
+ handleSIGHUP: true,
+ executablePath: defaultBrowserOptions.executablePath || browserType.executablePath(),
+ logger: undefined,
+ };
+ const options = {
+ playwrightPath,
+ browserTypeName: browserType.name(),
+ launchOptions,
+ ...extraOptions,
+ };
+ this._child = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), JSON.stringify(options)]);
+ this._child.on('error', (...args) => console.log("ERROR", ...args));
+ this._exitPromise = new Promise(resolve => this._child.on('exit', (exitCode, signal) => {
+ this._didExit = true;
+ resolve(exitCode);
+ }));
+
+ let outputString = '';
+ this._child.stdout.on('data', data => {
+ outputString += data.toString();
+ // Uncomment to debug.
+ // console.log(data.toString());
+ let match;
+ while (match = outputString.match(/\(([^()]+)=>([^()]+)\)/)) {
+ const key = match[1];
+ const value = match[2];
+ this._addOutput(key, value);
+ outputString = outputString.substring(match.index + match[0].length);
+ }
+ });
+
+ this._wsEndpoint = await this.out('wsEndpoint');
+ }
+
+ _addOutput(key, value) {
+ this._output.set(key, value);
+ const cb = this._outputCallback.get(key);
+ this._outputCallback.delete(key);
+ if (cb)
+ cb();
+ }
+
+ async out(key) {
+ if (!this._output.has(key))
+ await new Promise(f => this._outputCallback.set(key, f));
+ return this._output.get(key);
+ }
+
+ wsEndpoint() {
+ return this._wsEndpoint;
+ }
+
+ child() {
+ return this._child;
+ }
+
+ async childExitCode() {
+ return await this._exitPromise;
+ }
+
+ async _close() {
+ if (!this._didExit)
+ this._child.kill();
+ return await this.childExitCode();
+ }
+}
+
+registerFixture('remoteServer', async ({browserType, defaultBrowserOptions}, test) => {
+ const remoteServer = new RemoteServer();
+ await remoteServer._start(browserType, defaultBrowserOptions);
+ await test(remoteServer);
+ await remoteServer._close();
+});
+
+registerFixture('stallingRemoteServer', async ({browserType, defaultBrowserOptions}, test) => {
+ const remoteServer = new RemoteServer();
+ await remoteServer._start(browserType, defaultBrowserOptions, { stallOnClose: true });
+ await test(remoteServer);
+ await remoteServer._close();
+});