[go: nahoru, domu]

blob: cf54a87c8d7a4539f6e128f173bd6c6872e062e4 [file] [log] [blame]
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/es_modules_import */
import {type ElementHandle} from 'puppeteer';
import {openPanelViaMoreTools} from '../../../test/e2e/helpers/settings-helpers.js';
import {
$,
click,
clickElement,
getBrowserAndPages,
getTestServerPort,
goToResource,
timeout,
waitFor,
waitForAria,
} from '../../../test/shared/helper.js';
import {assertMatchesJSONSnapshot} from '../../../test/shared/snapshots.js';
import {platform} from '../../../test/shared/helper.js';
import {type UserFlow} from '../../../front_end/panels/recorder/models/Schema.js';
import type * as Recorder from '../../../front_end/panels/recorder/recorder.js';
const RECORDER_CONTROLLER_TAG_NAME = 'devtools-recorder-controller';
const TEST_RECORDING_NAME = 'New Recording';
const ControlOrMeta = platform === 'mac' ? 'Meta' : 'Control';
export async function getRecordingController() {
return (await waitFor(
RECORDER_CONTROLLER_TAG_NAME,
)) as unknown as ElementHandle<Recorder.RecorderController.RecorderController>;
}
export async function onRecordingStateChanged(): Promise<unknown> {
const view = await getRecordingController();
return view.evaluate(el => {
return new Promise(resolve => {
el.addEventListener(
'recordingstatechanged',
(event: Event) => resolve(
(event as Recorder.RecorderEvents.RecordingStateChangedEvent).recording,
),
{once: true},
);
});
});
}
export async function onRecorderAttachedToTarget(): Promise<unknown> {
const {frontend} = getBrowserAndPages();
return frontend.evaluate(() => {
return new Promise(resolve => {
window.addEventListener('recorderAttachedToTarget', resolve, {
once: true,
});
});
});
}
export async function onReplayFinished(): Promise<unknown> {
const view = await getRecordingController();
return view.evaluate(el => {
return new Promise(resolve => {
el.addEventListener('replayfinished', resolve, {once: true});
});
});
}
export async function enableUntrustedEventMode() {
const {frontend} = getBrowserAndPages();
await frontend.evaluate(() => {
// TODO: have an explicit UI setting or perhaps a special event to configure this
// instead of having a global setting.
// @ts-ignore
globalThis.Common.settings.createSetting('untrustedRecorderEvents', true);
});
}
export async function enableAndOpenRecorderPanel(path: string) {
await goToResource(path);
await openPanelViaMoreTools('Recorder');
await waitFor(RECORDER_CONTROLLER_TAG_NAME);
}
async function createRecording(name: string, selectorAttribute?: string) {
const newRecordingButton = await waitForAria('Create a new recording');
await newRecordingButton.click();
const input = await waitForAria('RECORDING NAME');
await input.type(name);
if (selectorAttribute) {
const input = await waitForAria(
'SELECTOR ATTRIBUTE https://g.co/devtools/recorder#selector',
);
await input.type(selectorAttribute);
}
}
export async function createAndStartRecording(
name: string,
selectorAttribute?: string,
) {
await createRecording(name, selectorAttribute);
const onRecordingStarted = onRecordingStateChanged();
await click('devtools-control-button');
await waitFor('devtools-recording-view');
await onRecordingStarted;
}
export async function changeNetworkConditions(condition: string) {
const {frontend} = getBrowserAndPages();
await frontend.waitForSelector('pierce/#tab-network');
await frontend.click('pierce/#tab-network');
await frontend.waitForSelector('pierce/[aria-label="Throttling"]');
await frontend.select('pierce/[aria-label="Throttling"] select', condition);
}
export async function openRecorderPanel() {
await click('[aria-label="Recorder"]');
await waitFor('devtools-recording-view');
}
type StartRecordingOptions = {
networkCondition?: string,
untrustedEvents?: boolean,
selectorAttribute?: string,
};
export async function startRecording(
path: string,
options: StartRecordingOptions = {
networkCondition: '',
untrustedEvents: false,
},
) {
if (options.networkCondition) {
await changeNetworkConditions(options.networkCondition);
}
await enableAndOpenRecorderPanel(path);
if (options.untrustedEvents) {
await enableUntrustedEventMode();
}
await createAndStartRecording(TEST_RECORDING_NAME, options.selectorAttribute);
}
export async function stopRecording(): Promise<unknown> {
const {frontend} = getBrowserAndPages();
await frontend.bringToFront();
const onRecordingStopped = onRecordingStateChanged();
await click('aria/End recording');
return await onRecordingStopped;
}
interface RecordingSnapshotOptions {
/**
* Whether to keep the offsets for recording or not.
*
* @defaultValue `false`
*/
offsets?: boolean;
}
const preprocessRecording = (
recording: unknown,
options: RecordingSnapshotOptions = {},
): unknown => {
let value = JSON.stringify(recording).replaceAll(
`:${getTestServerPort()}`,
':<test-port>',
);
value = value.replaceAll('\u200b', '');
if (!options.offsets) {
value = value.replaceAll(
/,?"(?:offsetY|offsetX)":[0-9]+(?:\.[0-9]+)?/g,
'',
);
}
return JSON.parse(value.trim());
};
export const assertRecordingMatchesSnapshot = (
recording: unknown,
options: RecordingSnapshotOptions = {},
) => {
assertMatchesJSONSnapshot(preprocessRecording(recording, options));
};
async function setCode(flow: string) {
const view = await getRecordingController();
await view.evaluate((el, flow) => {
el.dispatchEvent(new CustomEvent('setrecording', {detail: flow}));
}, flow);
}
async function waitForDialogAnimationEnd(root?: ElementHandle) {
const ANIMATION_TIMEOUT = 2000;
const dialog = await waitFor('dialog[open]', root);
const animationPromise = dialog.evaluate((dialog: Element) => {
return new Promise<void>(resolve => {
dialog.addEventListener('animationend', () => resolve(), {once: true});
});
});
await Promise.race([animationPromise, timeout(ANIMATION_TIMEOUT)]);
}
export async function clickSelectButtonItem(itemLabel: string, root: string) {
const selectMenu = await waitFor(root);
const selectMenuButton = await waitFor(
'devtools-select-menu-button',
selectMenu,
);
const selectMenuButtonArrow = await waitFor('#arrow', selectMenuButton);
const animationEndPromise = waitForDialogAnimationEnd();
await clickElement(selectMenuButtonArrow);
await animationEndPromise;
const selectMenuItems = await selectMenu.$$('pierce/devtools-menu-item');
const selectMenuItemIndex =
await Promise
.all(
selectMenuItems.map(
selectMenuItem => selectMenuItem.evaluate(element => element.textContent?.trim()),
),
)
.then(
elements => elements.findIndex(elementText => elementText === itemLabel),
);
if (selectMenuItemIndex === -1) {
throw new Error(
`Select menu item for label "${itemLabel}" is not found in "${root}"`,
);
}
await clickElement(selectMenuItems[selectMenuItemIndex]);
}
export async function setupRecorderWithScript(
script: UserFlow,
path: string = 'recorder/recorder.html',
): Promise<void> {
await enableAndOpenRecorderPanel(path);
await createAndStartRecording(script.title);
await stopRecording();
await setCode(JSON.stringify(script));
}
export async function setupRecorderWithScriptAndReplay(
script: UserFlow,
path: string = 'recorder/recorder.html',
): Promise<void> {
await setupRecorderWithScript(script, path);
const onceFinished = onReplayFinished();
await clickSelectButtonItem('Normal (Default)', 'devtools-replay-button');
await onceFinished;
}
export async function getCurrentRecording(): Promise<unknown> {
const controller = await $(RECORDER_CONTROLLER_TAG_NAME);
const recording = (await controller?.evaluate(
el => JSON.stringify((el as unknown as {getUserFlow(): unknown}).getUserFlow()),
)) as string;
return JSON.parse(recording);
}
export async function startOrStopRecordingShortcut(
execute: 'page'|'frontend' = 'frontend',
) {
const {target, frontend} = getBrowserAndPages();
const executeOn = execute === 'frontend' ? frontend : target;
const onRecordingStarted = onRecordingStateChanged();
await executeOn.bringToFront();
await executeOn.keyboard.down(ControlOrMeta);
await executeOn.keyboard.down('e');
await executeOn.keyboard.up(ControlOrMeta);
await executeOn.keyboard.up('e');
await waitFor('devtools-recording-view');
return await onRecordingStarted;
}
export async function fillCreateRecordingForm(path: string) {
await enableAndOpenRecorderPanel(path);
await createRecording(TEST_RECORDING_NAME);
}
export async function startRecordingViaShortcut(path: string) {
await enableAndOpenRecorderPanel(path);
await startOrStopRecordingShortcut();
}
export async function replayShortcut() {
const {frontend} = getBrowserAndPages();
await frontend.bringToFront();
await frontend.keyboard.down(ControlOrMeta);
await frontend.keyboard.down('Enter');
await frontend.keyboard.up(ControlOrMeta);
await frontend.keyboard.up('Enter');
}
export async function toggleCodeView() {
const {frontend} = getBrowserAndPages();
await frontend.bringToFront();
await frontend.keyboard.down(ControlOrMeta);
await frontend.keyboard.down('b');
await frontend.keyboard.up(ControlOrMeta);
await frontend.keyboard.up('b');
}