mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
test: unflake recorder tests (#2808)
We ensure that recorder is installed in the main frame before running the test.
This commit is contained in:
parent
baaa65492b
commit
35cb20d5ad
@ -18,26 +18,37 @@ import { Writable } from 'stream';
|
|||||||
import { BrowserContextBase } from '../browserContext';
|
import { BrowserContextBase } from '../browserContext';
|
||||||
import { Events } from '../events';
|
import { Events } from '../events';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
|
import * as js from '../javascript';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
import { RecorderController } from './recorderController';
|
import { RecorderController } from './recorderController';
|
||||||
|
import DebugScript from './injected/debugScript';
|
||||||
|
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
|
private _options: { recorderOutput?: Writable | undefined };
|
||||||
|
|
||||||
constructor(context: BrowserContextBase, options: { recorderOutput?: Writable | undefined }) {
|
constructor(context: BrowserContextBase, options: { recorderOutput?: Writable | undefined }) {
|
||||||
const installInFrame = async (frame: frames.Frame) => {
|
this._options = options;
|
||||||
try {
|
|
||||||
const mainContext = await frame._mainContext();
|
|
||||||
await mainContext.createDebugScript({ console: true, record: !!options.recorderOutput });
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.recorderOutput)
|
if (options.recorderOutput)
|
||||||
new RecorderController(context, options.recorderOutput);
|
new RecorderController(context, options.recorderOutput);
|
||||||
|
|
||||||
context.on(Events.BrowserContext.Page, (page: Page) => {
|
context.on(Events.BrowserContext.Page, (page: Page) => {
|
||||||
for (const frame of page.frames())
|
for (const frame of page.frames())
|
||||||
installInFrame(frame);
|
this.ensureInstalledInFrame(frame);
|
||||||
page.on(Events.Page.FrameNavigated, installInFrame);
|
page.on(Events.Page.FrameNavigated, frame => this.ensureInstalledInFrame(frame));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureInstalledInFrame(frame: frames.Frame): Promise<js.JSHandle<DebugScript> | undefined> {
|
||||||
|
try {
|
||||||
|
const mainContext = await frame._mainContext();
|
||||||
|
return await mainContext.createDebugScript({ console: true, record: !!this._options.recorderOutput });
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureInstalledInFrameForTest(frame: frames.Frame): Promise<void> {
|
||||||
|
const handle = await this.ensureInstalledInFrame(frame);
|
||||||
|
await handle!.evaluate(debugScript => debugScript.recorder!.refreshListeners());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,6 @@ export default class DebugScript {
|
|||||||
consoleAPI: ConsoleAPI | undefined;
|
consoleAPI: ConsoleAPI | undefined;
|
||||||
recorder: Recorder | undefined;
|
recorder: Recorder | undefined;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(injectedScript: InjectedScript, options: { console?: boolean, record?: boolean }) {
|
initialize(injectedScript: InjectedScript, options: { console?: boolean, record?: boolean }) {
|
||||||
if (options.console)
|
if (options.console)
|
||||||
this.consoleAPI = new ConsoleAPI(injectedScript);
|
this.consoleAPI = new ConsoleAPI(injectedScript);
|
||||||
|
@ -28,13 +28,27 @@ declare global {
|
|||||||
export class Recorder {
|
export class Recorder {
|
||||||
private _injectedScript: InjectedScript;
|
private _injectedScript: InjectedScript;
|
||||||
private _performingAction = false;
|
private _performingAction = false;
|
||||||
|
readonly refreshListeners: () => void;
|
||||||
|
|
||||||
constructor(injectedScript: InjectedScript) {
|
constructor(injectedScript: InjectedScript) {
|
||||||
this._injectedScript = injectedScript;
|
this._injectedScript = injectedScript;
|
||||||
|
|
||||||
document.addEventListener('click', event => this._onClick(event), true);
|
const onClick = this._onClick.bind(this);
|
||||||
document.addEventListener('input', event => this._onInput(event), true);
|
const onInput = this._onInput.bind(this);
|
||||||
document.addEventListener('keydown', event => this._onKeyDown(event), true);
|
const onKeyDown = this._onKeyDown.bind(this);
|
||||||
|
this.refreshListeners = () => {
|
||||||
|
document.removeEventListener('click', onClick, true);
|
||||||
|
document.removeEventListener('input', onInput, true);
|
||||||
|
document.removeEventListener('keydown', onKeyDown, true);
|
||||||
|
document.addEventListener('click', onClick, true);
|
||||||
|
document.addEventListener('input', onInput, true);
|
||||||
|
document.addEventListener('keydown', onKeyDown, true);
|
||||||
|
};
|
||||||
|
this.refreshListeners();
|
||||||
|
// Document listeners are cleared upon document.open,
|
||||||
|
// so we refresh them periodically in a best-effort manner.
|
||||||
|
// Note: keep in sync with the same constant in the test.
|
||||||
|
setInterval(this.refreshListeners, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onClick(event: MouseEvent) {
|
private async _onClick(event: MouseEvent) {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {USES_HOOKS} = require('./utils').testOptions(browserType);
|
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = require('./utils').testOptions(browserType);
|
||||||
|
|
||||||
class WritableBuffer {
|
class WritableBuffer {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -56,16 +56,20 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
state.context = await state.browser.newContext();
|
state.context = await state.browser.newContext();
|
||||||
state.output = new WritableBuffer();
|
state.output = new WritableBuffer();
|
||||||
const debugController = state.context._initDebugModeForTest({ recorderOutput: state.output });
|
const debugController = state.context._initDebugModeForTest({ recorderOutput: state.output });
|
||||||
|
state.page = await state.context.newPage();
|
||||||
|
state.setContent = async (content) => {
|
||||||
|
await state.page.setContent(content);
|
||||||
|
await debugController.ensureInstalledInFrameForTest(state.page.mainFrame());
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async state => {
|
afterEach(async state => {
|
||||||
await state.context.close();
|
await state.context.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should click', async function({context, output, server}) {
|
it('should click', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<button onclick="console.log('click')">Submit</button>`);
|
await setContent(`<button onclick="console.log('click')">Submit</button>`);
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('click'),
|
output.waitFor('click'),
|
||||||
@ -77,10 +81,30 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fill', async function({context, output, server}) {
|
it('should click after document.open', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<input id="input" name="name" oninput="console.log(input.value)"></input>`);
|
await setContent(``);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.open();
|
||||||
|
document.write(`<button onclick="console.log('click')">Submit</button>`);
|
||||||
|
document.close();
|
||||||
|
// Give it time to refresh. See Recorder for details.
|
||||||
|
return new Promise(f => setTimeout(f, 1000));
|
||||||
|
});
|
||||||
|
const [message] = await Promise.all([
|
||||||
|
page.waitForEvent('console'),
|
||||||
|
output.waitFor('click'),
|
||||||
|
page.dispatchEvent('button', 'click', { detail: 1 })
|
||||||
|
]);
|
||||||
|
expect(output.text()).toContain(`
|
||||||
|
// Click text="Submit"
|
||||||
|
await page.click('text="Submit"');`);
|
||||||
|
expect(message.text()).toBe('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fill', async function({page, output, setContent, server}) {
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await setContent(`<input id="input" name="name" oninput="console.log(input.value)"></input>`);
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('fill'),
|
output.waitFor('fill'),
|
||||||
@ -92,10 +116,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe('John');
|
expect(message.text()).toBe('John');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should press', async function({context, output, server}) {
|
it('should press', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<input name="name" onkeypress="console.log('press')"></input>`);
|
await setContent(`<input name="name" onkeypress="console.log('press')"></input>`);
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('press'),
|
output.waitFor('press'),
|
||||||
@ -107,10 +130,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe('press');
|
expect(message.text()).toBe('press');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check', async function({context, output, server}) {
|
it('should check', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`);
|
await setContent(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`);
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('check'),
|
output.waitFor('check'),
|
||||||
@ -123,10 +145,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe("true");
|
expect(message.text()).toBe("true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should uncheck', async function({context, output, server}) {
|
it('should uncheck', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<input id="checkbox" type="checkbox" checked name="accept" onchange="console.log(checkbox.checked)"></input>`);
|
await setContent(`<input id="checkbox" type="checkbox" checked name="accept" onchange="console.log(checkbox.checked)"></input>`);
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('uncheck'),
|
output.waitFor('uncheck'),
|
||||||
@ -138,10 +159,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe("false");
|
expect(message.text()).toBe("false");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select', async function({context, output, server}) {
|
it('should select', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent('<select id="age" onchange="console.log(age.selectedOptions[0].value)"><option value="1"><option value="2"></select>');
|
await setContent('<select id="age" onchange="console.log(age.selectedOptions[0].value)"><option value="1"><option value="2"></select>');
|
||||||
const [message] = await Promise.all([
|
const [message] = await Promise.all([
|
||||||
page.waitForEvent('console'),
|
page.waitForEvent('console'),
|
||||||
output.waitFor('select'),
|
output.waitFor('select'),
|
||||||
@ -153,10 +173,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(message.text()).toBe("2");
|
expect(message.text()).toBe("2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should await popup', async function({context, output, server}) {
|
it('should await popup', async function({context, page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
|
await setContent('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
|
||||||
const [popup] = await Promise.all([
|
const [popup] = await Promise.all([
|
||||||
context.waitForEvent('page'),
|
context.waitForEvent('page'),
|
||||||
output.waitFor('waitForEvent'),
|
output.waitFor('waitForEvent'),
|
||||||
@ -171,10 +190,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
|
|||||||
expect(popup.url()).toBe(`${server.PREFIX}/popup/popup.html`);
|
expect(popup.url()).toBe(`${server.PREFIX}/popup/popup.html`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should await navigation', async function({context, output, server}) {
|
it('should await navigation', async function({page, output, setContent, server}) {
|
||||||
const page = await context.newPage();
|
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`<a onclick="setTimeout(() => window.location.href='${server.PREFIX}/popup/popup.html', 1000)">link</a>`);
|
await setContent(`<a onclick="setTimeout(() => window.location.href='${server.PREFIX}/popup/popup.html', 1000)">link</a>`);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
output.waitFor('waitForNavigation'),
|
output.waitFor('waitForNavigation'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user