[go: nahoru, domu]

blob: 56541fc936d2d376993a64650d7d9ca9416e3fd2 [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 {assert} from 'chai';
import {
type StepType,
type AssertedEventType,
} from '../../../front_end/panels/recorder/models/Schema.js';
import {
getBrowserAndPages,
getResourcesPath,
getTestServerPort,
waitFor,
click,
} from '../../../test/shared/helper.js';
import {
describe,
it,
} from '../../../test/shared/mocha-extensions.js';
import {
clickSelectButtonItem,
onReplayFinished,
replayShortcut,
setupRecorderWithScript,
setupRecorderWithScriptAndReplay,
} from './helpers.js';
describe('Recorder', function() {
if (this.timeout() !== 0) {
this.timeout(40000);
}
describe('Replay', () => {
it('should navigate to the url of the first section', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder2.html`,
},
],
});
assert.strictEqual(
target.url(),
`${getResourcesPath()}/recorder/recorder2.html`,
);
});
it('should be able to replay click steps', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder.html`,
},
{
type: 'click' as StepType.Click,
selectors: ['a[href="recorder2.html"]'],
offsetX: 1,
offsetY: 1,
assertedEvents: [
{
type: 'navigation' as AssertedEventType.Navigation,
url: `${getResourcesPath()}/recorder/recorder.html`,
},
],
},
],
});
assert.strictEqual(
target.url(),
`${getResourcesPath()}/recorder/recorder2.html`,
);
});
it('should be able to replay click steps on checkboxes', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/checkbox.html`,
},
{
type: 'click' as StepType.Click,
selectors: ['input'],
offsetX: 1,
offsetY: 1,
},
],
});
assert.strictEqual(
await target.evaluate(() => document.querySelector('input')?.checked),
true,
);
});
it('should be able to replay keyboard events', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/input.html`,
},
{type: 'keyDown' as StepType.KeyDown, target: 'main', key: 'Tab'},
{type: 'keyUp' as StepType.KeyUp, target: 'main', key: 'Tab'},
{type: 'keyDown' as StepType.KeyDown, target: 'main', key: '1'},
{type: 'keyUp' as StepType.KeyUp, target: 'main', key: '1'},
{type: 'keyDown' as StepType.KeyDown, target: 'main', key: 'Tab'},
{type: 'keyUp' as StepType.KeyUp, target: 'main', key: 'Tab'},
{type: 'keyDown' as StepType.KeyDown, target: 'main', key: '2'},
{type: 'keyUp' as StepType.KeyUp, target: 'main', key: '2'},
],
});
const value = await target.$eval(
'#log',
e => (e as HTMLElement).innerText.trim(),
);
assert.strictEqual(value, ['one:1', 'two:2'].join('\n'));
});
it('should be able to replay events on select', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/select.html`,
},
{
type: 'change' as StepType.Change,
target: 'main',
selectors: ['aria/Select'],
value: 'O2',
},
],
});
const value = await target.$eval(
'#select',
e => (e as HTMLSelectElement).value,
);
assert.strictEqual(value, 'O2');
});
it('should be able to replay events on non text inputs', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/input.html`,
},
{
type: 'change' as StepType.Change,
target: 'main',
selectors: ['#color'],
value: '#333333',
},
],
});
const value = await target.$eval(
'#color',
e => (e as HTMLSelectElement).value,
);
assert.strictEqual(value, '#333333');
});
it('should be able to replay events with text selectors', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/iframe1.html`,
},
{
type: 'click' as StepType.Click,
target: 'main',
selectors: ['text/To'],
offsetX: 0,
offsetY: 0,
},
],
});
const frame = target.frames().find(
frame => frame.url() === `${getResourcesPath()}/recorder/iframe2.html`,
);
assert.ok(frame, 'Frame that the target page navigated to is not found');
});
it('should be able to replay events with xpath selectors', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/iframe1.html`,
},
{
type: 'click' as StepType.Click,
target: 'main',
selectors: ['xpath//html/body/a'],
offsetX: 0,
offsetY: 0,
},
],
});
const frame = target.frames().find(
frame => frame.url() === `${getResourcesPath()}/recorder/iframe2.html`,
);
assert.ok(frame, 'Frame that the target page navigated to is not found');
});
it('should be able to override the value in text inputs that have a value already', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/input.html`,
},
{
type: 'change' as StepType.Change,
target: 'main',
selectors: ['#prefilled'],
value: 'cba',
},
],
});
const value = await target.$eval(
'#prefilled',
e => (e as HTMLSelectElement).value,
);
assert.strictEqual(value, 'cba');
});
it('should be able to override the value in text inputs that are partially prefilled', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/input.html`,
},
{
type: 'change' as StepType.Change,
target: 'main',
selectors: ['#partially-prefilled'],
value: 'abcdef',
},
],
});
const value = await target.$eval(
'#partially-prefilled',
e => (e as HTMLSelectElement).value,
);
assert.strictEqual(value, 'abcdef');
});
it('should be able to replay viewport change', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/select.html`,
},
{
type: 'setViewport' as StepType.SetViewport,
width: 800,
height: 600,
isLandscape: false,
isMobile: false,
deviceScaleFactor: 1,
hasTouch: false,
},
],
});
assert.strictEqual(
await target.evaluate(() => window.visualViewport?.width),
800,
);
assert.strictEqual(
await target.evaluate(() => window.visualViewport?.height),
600,
);
});
it('should be able to replay scroll events', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/scroll.html`,
},
{
type: 'setViewport' as StepType.SetViewport,
width: 800,
height: 600,
isLandscape: false,
isMobile: false,
deviceScaleFactor: 1,
hasTouch: false,
},
{
type: 'scroll' as StepType.Scroll,
target: 'main',
selectors: ['body > div:nth-child(1)'],
x: 0,
y: 40,
},
{type: 'scroll' as StepType.Scroll, target: 'main', x: 40, y: 40},
],
});
assert.strictEqual(await target.evaluate(() => window.pageXOffset), 40);
assert.strictEqual(await target.evaluate(() => window.pageYOffset), 40);
assert.strictEqual(
await target.evaluate(
() => document.querySelector('#overflow')?.scrollTop,
),
40,
);
assert.strictEqual(
await target.evaluate(
() => document.querySelector('#overflow')?.scrollLeft,
),
0,
);
});
it('should be able to scroll into view when needed', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'setViewport' as StepType.SetViewport,
width: 800,
height: 600,
isLandscape: false,
isMobile: false,
deviceScaleFactor: 1,
hasTouch: false,
},
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/scroll-into-view.html`,
},
{
type: 'click' as StepType.Click,
selectors: [['button']],
offsetX: 1,
offsetY: 1,
},
],
});
assert.strictEqual(
await target.evaluate(
() => document.querySelector('button')?.innerText,
),
'clicked',
);
});
it('should be able to replay ARIA selectors on inputs', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/form.html`,
},
{
type: 'setViewport' as StepType.SetViewport,
width: 800,
height: 600,
isLandscape: false,
isMobile: false,
deviceScaleFactor: 1,
hasTouch: false,
},
{
type: 'click' as StepType.Click,
target: 'main',
selectors: ['aria/Name:'],
offsetX: 1,
offsetY: 1,
},
],
});
assert.strictEqual(
await target.evaluate(() => document.activeElement?.id),
'name',
);
});
it('should be able to waitForElement', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/shadow-dynamic.html`,
},
{
type: 'waitForElement' as StepType.WaitForElement,
selectors: [['custom-element', 'button']],
},
{
type: 'click' as StepType.Click,
target: 'main',
selectors: [['custom-element', 'button']],
offsetX: 1,
offsetY: 1,
},
{
type: 'waitForElement' as StepType.WaitForElement,
selectors: [['custom-element', 'button']],
operator: '>=',
count: 2,
},
],
});
assert.strictEqual(
await target.evaluate(
() => document.querySelectorAll('custom-element').length,
),
2,
);
});
it('should be able to waitForExpression', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/shadow-dynamic.html`,
},
{
type: 'click' as StepType.Click,
target: 'main',
selectors: [['custom-element', 'button']],
offsetX: 1,
offsetY: 1,
},
{
type: 'waitForExpression' as StepType.WaitForExpression,
target: 'main',
expression: 'document.querySelectorAll("custom-element").length === 2',
},
],
});
assert.strictEqual(
await target.evaluate(
() => document.querySelectorAll('custom-element').length,
),
2,
);
});
it('should show PerformancePanel if the MeasurePerformance SelectMenu is clicked for replay', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScript({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder2.html`,
},
],
});
const onceFinished = onReplayFinished();
await click('aria/Performance panel');
await onceFinished;
assert.strictEqual(
target.url(),
`${getResourcesPath()}/recorder/recorder2.html`,
);
await waitFor('[aria-label="Performance panel"]');
});
it('should be able to replay actions with popups', async () => {
const {browser} = getBrowserAndPages();
const events: Array<{type: string, url: string}> = [];
// We can't import 'puppeteer' here because its not listed in the tsconfig.json of
// the test target.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetLifecycleHandler = (target: any, type: string) => {
if (!target.url().endsWith('popup.html')) {
return;
}
events.push({type, url: target.url()});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetCreatedHandler = (target: any) => targetLifecycleHandler(target, 'targetCreated');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetDestroyedHandler = (target: any) => targetLifecycleHandler(target, 'targetDestroyed');
browser.on('targetcreated', targetCreatedHandler);
browser.on('targetdestroyed', targetDestroyedHandler);
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder.html`,
assertedEvents: [
{
title: '',
type: 'navigation' as AssertedEventType.Navigation,
url: 'https://<url>/test/e2e/resources/recorder/recorder.html',
},
],
},
{
type: 'click' as StepType.Click,
selectors: [['aria/Open Popup'], ['#popup']],
target: 'main',
offsetX: 1,
offsetY: 1,
},
{
type: 'click' as StepType.Click,
selectors: [['aria/Button in Popup'], ['body > button']],
target: `${getResourcesPath()}/recorder/popup.html`,
offsetX: 1,
offsetY: 1,
},
{
type: 'close' as StepType.Close,
target: `${getResourcesPath()}/recorder/popup.html`,
},
],
});
assert.deepEqual(events, [
{
type: 'targetCreated',
url: `${getResourcesPath()}/recorder/popup.html`,
},
{
type: 'targetDestroyed',
url: `${getResourcesPath()}/recorder/popup.html`,
},
]);
browser.off('targetcreated', targetCreatedHandler);
browser.off('targetdestroyed', targetDestroyedHandler);
});
it('should record interactions with OOPIFs', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScriptAndReplay({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `https://localhost:${getTestServerPort()}/test/e2e/resources/recorder/oopif.html`,
assertedEvents: [
{
title: '',
type: 'navigation' as AssertedEventType.Navigation,
url: `https://localhost:${getTestServerPort()}/test/e2e/resources/recorder/oopif.html`,
},
],
},
{
type: 'click' as StepType.Click,
target: `https://devtools.oopif.test:${getTestServerPort()}/test/e2e/resources/recorder/iframe1.html`,
selectors: [['aria/To iframe 2'], ['body > a']],
offsetX: 1,
offsetY: 1,
assertedEvents: [
{
type: 'navigation' as AssertedEventType.Navigation,
title: '',
url: `https://devtools.oopif.test:${getTestServerPort()}/test/e2e/resources/recorder/iframe2.html`,
},
],
},
],
});
const frame = target.frames().find(
frame => frame.url() ===
`https://devtools.oopif.test:${getTestServerPort()}/test/e2e/resources/recorder/iframe2.html`,
);
assert.ok(frame, 'Frame that the target page navigated to is not found');
});
it('should replay when clicked on slow replay', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScript({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder.html`,
},
{
type: 'click' as StepType.Click,
selectors: ['a[href="recorder2.html"]'],
offsetX: 1,
offsetY: 1,
assertedEvents: [
{
type: 'navigation' as AssertedEventType.Navigation,
url: `${getResourcesPath()}/recorder/recorder.html`,
},
],
},
],
});
const onceFinished = onReplayFinished();
await clickSelectButtonItem('Slow', 'devtools-replay-button');
await onceFinished;
assert.strictEqual(
target.url(),
`${getResourcesPath()}/recorder/recorder2.html`,
);
});
});
it('should be able to start a replay with shortcut', async () => {
const {target} = getBrowserAndPages();
await setupRecorderWithScript({
title: 'Test Recording',
steps: [
{
type: 'navigate' as StepType.Navigate,
url: `${getResourcesPath()}/recorder/recorder2.html`,
},
],
});
const onceFinished = onReplayFinished();
await replayShortcut();
await onceFinished;
assert.strictEqual(
target.url(),
`${getResourcesPath()}/recorder/recorder2.html`,
);
});
});