test: unflake recorder tests (#2808)

We ensure that recorder is installed in the main frame before running the test.
This commit is contained in:
Dmitry Gozman 2020-07-07 14:11:59 -07:00 committed by GitHub
parent baaa65492b
commit 35cb20d5ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 42 deletions

View File

@ -18,26 +18,37 @@ import { Writable } from 'stream';
import { BrowserContextBase } from '../browserContext';
import { Events } from '../events';
import * as frames from '../frames';
import * as js from '../javascript';
import { Page } from '../page';
import { RecorderController } from './recorderController';
import DebugScript from './injected/debugScript';
export class DebugController {
private _options: { recorderOutput?: Writable | undefined };
constructor(context: BrowserContextBase, options: { recorderOutput?: Writable | undefined }) {
const installInFrame = async (frame: frames.Frame) => {
try {
const mainContext = await frame._mainContext();
await mainContext.createDebugScript({ console: true, record: !!options.recorderOutput });
} catch (e) {
}
};
this._options = options;
if (options.recorderOutput)
new RecorderController(context, options.recorderOutput);
context.on(Events.BrowserContext.Page, (page: Page) => {
for (const frame of page.frames())
installInFrame(frame);
page.on(Events.Page.FrameNavigated, installInFrame);
this.ensureInstalledInFrame(frame);
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());
}
}

View File

@ -22,9 +22,6 @@ export default class DebugScript {
consoleAPI: ConsoleAPI | undefined;
recorder: Recorder | undefined;
constructor() {
}
initialize(injectedScript: InjectedScript, options: { console?: boolean, record?: boolean }) {
if (options.console)
this.consoleAPI = new ConsoleAPI(injectedScript);

View File

@ -28,13 +28,27 @@ declare global {
export class Recorder {
private _injectedScript: InjectedScript;
private _performingAction = false;
readonly refreshListeners: () => void;
constructor(injectedScript: InjectedScript) {
this._injectedScript = injectedScript;
document.addEventListener('click', event => this._onClick(event), true);
document.addEventListener('input', event => this._onInput(event), true);
document.addEventListener('keydown', event => this._onKeyDown(event), true);
const onClick = this._onClick.bind(this);
const onInput = this._onInput.bind(this);
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) {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
const {USES_HOOKS} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = require('./utils').testOptions(browserType);
class WritableBuffer {
constructor() {
@ -56,16 +56,20 @@ describe.skip(USES_HOOKS)('Recorder', function() {
state.context = await state.browser.newContext();
state.output = new WritableBuffer();
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 => {
await state.context.close();
});
it('should click', async function({context, output, server}) {
const page = await context.newPage();
it('should click', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('click'),
@ -77,10 +81,30 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe('click');
});
it('should fill', async function({context, output, server}) {
const page = await context.newPage();
it('should click after document.open', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('fill'),
@ -92,10 +116,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe('John');
});
it('should press', async function({context, output, server}) {
const page = await context.newPage();
it('should press', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('press'),
@ -107,10 +130,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe('press');
});
it('should check', async function({context, output, server}) {
const page = await context.newPage();
it('should check', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('check'),
@ -123,10 +145,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe("true");
});
it('should uncheck', async function({context, output, server}) {
const page = await context.newPage();
it('should uncheck', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('uncheck'),
@ -138,10 +159,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe("false");
});
it('should select', async function({context, output, server}) {
const page = await context.newPage();
it('should select', async function({page, output, setContent, server}) {
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([
page.waitForEvent('console'),
output.waitFor('select'),
@ -153,10 +173,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(message.text()).toBe("2");
});
it('should await popup', async function({context, output, server}) {
const page = await context.newPage();
it('should await popup', async function({context, page, output, setContent, server}) {
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([
context.waitForEvent('page'),
output.waitFor('waitForEvent'),
@ -171,10 +190,9 @@ describe.skip(USES_HOOKS)('Recorder', function() {
expect(popup.url()).toBe(`${server.PREFIX}/popup/popup.html`);
});
it('should await navigation', async function({context, output, server}) {
const page = await context.newPage();
it('should await navigation', async function({page, output, setContent, server}) {
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([
page.waitForNavigation(),
output.waitFor('waitForNavigation'),