mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-13 07:35:33 +03:00
test: add some tests for remote connect (#3614)
Our current tests excercise the same-process launchServer/connect combination. This change adds tests for remote server and immediately exposes a bug with selector engines.
This commit is contained in:
parent
db0fa07330
commit
25381cfa43
46
test/browsertype-connect-subprocess.spec.ts
Normal file
46
test/browsertype-connect-subprocess.spec.ts
Normal file
@ -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(`<div>hello</div>`);
|
||||
expect(await page.innerHTML('css=div')).toBe('hello');
|
||||
expect(await page.innerHTML('mycss=div')).toBe('hello');
|
||||
await browser.close();
|
||||
});
|
@ -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(`<div>hello</div>`);
|
||||
expect(await page.innerHTML('css=div')).toBe('hello');
|
||||
expect(await page.innerHTML('mycss=div')).toBe('hello');
|
||||
await browserServer.close();
|
||||
});
|
||||
|
@ -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<any, any>;
|
||||
_outputCallback: Map<any, any>;
|
||||
_browserType: BrowserType<Browser>;
|
||||
_child: import("child_process").ChildProcess;
|
||||
_exitPromise: Promise<unknown>;
|
||||
_exitAndDisconnectPromise: Promise<any>;
|
||||
constructor(browserType: BrowserType<Browser>, 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);
|
||||
|
131
test/remoteServer.fixture.ts
Normal file
131
test/remoteServer.fixture.ts
Normal file
@ -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<any, any>;
|
||||
_outputCallback: Map<any, any>;
|
||||
_browserType: BrowserType<Browser>;
|
||||
_child: import("child_process").ChildProcess;
|
||||
_exitPromise: Promise<unknown>;
|
||||
_exitAndDisconnectPromise: Promise<any>;
|
||||
_browser: Browser;
|
||||
_didExit: boolean;
|
||||
_wsEndpoint: string;
|
||||
|
||||
async _start(browserType: BrowserType<Browser>, 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();
|
||||
});
|
Loading…
Reference in New Issue
Block a user