feat(ui): render all console / network messages in trace (#24115)

This commit is contained in:
Pavel Feldman 2023-07-10 12:56:56 -07:00 committed by GitHub
parent 74cf869c03
commit 67ad2c2bf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 205 additions and 166 deletions

View File

@ -73,11 +73,11 @@
}
.call-log-url {
color: var(--blue);
color: var(--vscode-charts-blue);
}
.call-log-selector {
color: var(--orange);
color: var(--vscode-charts-orange);
white-space: nowrap;
}

View File

@ -62,12 +62,12 @@
display: inline;
flex: none;
padding-left: 5px;
color: var(--orange);
color: var(--vscode-charts-orange);
}
.action-url {
display: inline;
flex: none;
padding-left: 5px;
color: var(--blue);
color: var(--vscode-charts-blue);
}

View File

@ -18,9 +18,19 @@ import * as React from 'react';
import './attachmentsTab.css';
import { ImageDiffView } from '@web/components/imageDiffView';
import type { TestAttachment } from '@web/components/imageDiffView';
import type { ActionTraceEventInContext } from './modelUtil';
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
export const AttachmentsTab: React.FunctionComponent<{
model: MultiTraceModel | undefined,
}> = ({ model }) => {
if (!model)
return null;
return <div className='attachments-tab'>
{ model.actions.map((action, index) => <AttachmentsSection key={index} action={action} />) }
</div>;
};
export const AttachmentsSection: React.FunctionComponent<{
action: ActionTraceEventInContext | undefined,
}> = ({ action }) => {
if (!action)
@ -34,7 +44,7 @@ export const AttachmentsTab: React.FunctionComponent<{
const traceUrl = action.context.traceUrl;
return <div className='attachments-tab'>
return <>
{expected && actual && <div className='attachments-section'>Image diff</div>}
{expected && actual && <ImageDiffView imageDiff={{
name: 'Image diff',
@ -55,7 +65,7 @@ export const AttachmentsTab: React.FunctionComponent<{
<a target='_blank' href={attachmentURL(traceUrl, a)}>{a.name}</a>
</div>;
})}
</div>;
</>;
};
function attachmentURL(traceUrl: string, attachment: {

View File

@ -81,7 +81,7 @@
.call-value.datetime,
.call-value.string,
.call-value.locator {
color: var(--orange);
color: var(--vscode-charts-orange);
}
.call-value.number,
@ -91,7 +91,7 @@
.call-value.undefined,
.call-value.function,
.call-value.object {
color: var(--blue);
color: var(--vscode-charts-blue);
}
.call-tab .error-message {

View File

@ -16,40 +16,21 @@
.console-tab {
display: flex;
flex: auto;
line-height: 16px;
white-space: pre;
overflow: auto;
padding-top: 3px;
user-select: text;
}
.console-line {
flex: none;
padding: 3px 0 3px 3px;
align-items: center;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
}
.console-line.error {
background: var(--vscode-inputValidation-errorBackground);
border-top-color: var(--vscode-inputValidation-errorBorder);
border-bottom-color: var(--vscode-inputValidation-errorBorder);
color: var(--vscode-errorForeground);
}
.console-line.warning {
background: var(--vscode-inputValidation-warningBackground);
border-top-color: var(--vscode-inputValidation-warningBorder);
border-bottom-color: var(--vscode-inputValidation-warningBorder);
width: 100%;
}
.console-line .codicon {
padding: 0 2px 0 3px;
position: relative;
flex: none;
top: 1px;
top: 3px;
}
.console-line.warning .codicon {
@ -57,11 +38,10 @@
}
.console-line-message {
white-space: initial;
word-break: break-word;
white-space: pre-wrap;
position: relative;
top: -2px;
user-select: text;
}
.console-location {

View File

@ -19,58 +19,81 @@ import type { ActionTraceEvent } from '@trace/trace';
import * as React from 'react';
import './consoleTab.css';
import * as modelUtil from './modelUtil';
import { ListView } from '@web/components/listView';
type ConsoleEntry = {
message?: channels.ConsoleMessageInitializer;
error?: channels.SerializedError;
highlight: boolean;
};
const ConsoleListView = ListView<ConsoleEntry>;
export const ConsoleTab: React.FunctionComponent<{
model: modelUtil.MultiTraceModel | undefined,
action: ActionTraceEvent | undefined,
}> = ({ action }) => {
const entries = React.useMemo(() => {
if (!action)
return [];
const entries: { message?: channels.ConsoleMessageInitializer, error?: channels.SerializedError }[] = [];
const context = modelUtil.context(action);
for (const event of modelUtil.eventsForAction(action)) {
}> = ({ model, action }) => {
const { entries } = React.useMemo(() => {
if (!model)
return { entries: [] };
const entries: ConsoleEntry[] = [];
const actionEvents = action ? modelUtil.eventsForAction(action) : [];
for (const event of model.events) {
if (event.method !== 'console' && event.method !== 'pageError')
continue;
if (event.method === 'console') {
const { guid } = event.params.message;
entries.push({ message: context.initializers[guid] });
entries.push({
message: modelUtil.context(event).initializers[guid],
highlight: actionEvents.includes(event),
});
}
if (event.method === 'pageError') {
entries.push({
error: event.params.error,
highlight: actionEvents.includes(event),
});
}
if (event.method === 'pageError')
entries.push({ error: event.params.error });
}
return entries;
}, [action]);
return { entries };
}, [model, action]);
return <div className='console-tab'>{
entries.map((entry, index) => {
const { message, error } = entry;
if (message) {
const url = message.location.url;
const filename = url ? url.substring(url.lastIndexOf('/') + 1) : '<anonymous>';
return <div className={'console-line ' + message.type} key={index}>
<span className='console-location'>{filename}:{message.location.lineNumber}</span>
<span className={'codicon codicon-' + iconClass(message)}></span>
<span className='console-line-message'>{message.text}</span>
</div>;
}
if (error) {
const { error: errorObject, value } = error;
if (errorObject) {
return <div className='console-line error' key={index}>
<span className={'codicon codicon-error'}></span>
<span className='console-line-message'>{errorObject.message}</span>
<div className='console-stack'>{errorObject.stack}</div>
</div>;
} else {
return <div className='console-line error' key={index}>
<span className={'codicon codicon-error'}></span>
<span className='console-line-message'>{String(value)}</span>
return <div className='console-tab'>
<ConsoleListView
items={entries}
isError={entry => !!entry.error || entry.message?.type === 'error'}
isWarning={entry => entry.message?.type === 'warning'}
render={entry => {
const { message, error } = entry;
if (message) {
const url = message.location.url;
const filename = url ? url.substring(url.lastIndexOf('/') + 1) : '<anonymous>';
return <div className='console-line'>
<span className='console-location'>{filename}:{message.location.lineNumber}</span>
<span className={'codicon codicon-' + iconClass(message)}></span>
<span className='console-line-message'>{message.text}</span>
</div>;
}
}
return null;
})
}</div>;
if (error) {
const { error: errorObject, value } = error;
if (errorObject) {
return <div className='console-line'>
<span className={'codicon codicon-error'}></span>
<span className='console-line-message'>{errorObject.message}</span>
<div className='console-stack'>{errorObject.stack}</div>
</div>;
} else {
return <div className='console-line'>
<span className={'codicon codicon-error'}></span>
<span className='console-line-message'>{String(value)}</span>
</div>;
}
}
return null;
}}
isHighlighted={entry => !!entry.highlight}
/>
</div>;
};
function iconClass(message: channels.ConsoleMessageInitializer): string {

View File

@ -63,6 +63,7 @@ export class MultiTraceModel {
readonly sdkLanguage: Language | undefined;
readonly testIdAttributeName: string | undefined;
readonly sources: Map<string, SourceModel>;
resources: ResourceSnapshot[];
constructor(contexts: ContextEntry[]) {
@ -81,6 +82,7 @@ export class MultiTraceModel {
this.actions = mergeActions(contexts);
this.events = ([] as EventTraceEvent[]).concat(...contexts.map(c => c.events));
this.hasSource = contexts.some(c => c.hasSource);
this.resources = [...contexts.map(c => c.resources)].flat();
this.events.sort((a1, a2) => a1.time - a2.time);
this.sources = collectSources(this.actions);
@ -191,7 +193,7 @@ export function idForAction(action: ActionTraceEvent) {
return `${action.pageId || 'none'}:${action.callId}`;
}
export function context(action: ActionTraceEvent): ContextEntry {
export function context(action: ActionTraceEvent | EventTraceEvent): ContextEntry {
return (action as any)[contextSymbol];
}

View File

@ -23,10 +23,6 @@
outline: none;
}
.network-request.selected:focus {
border-color: var(--orange);
}
.network-request-title {
height: 28px;
display: flex;
@ -34,6 +30,10 @@
flex: 1;
}
.network-request.highlighted {
background-color: var(--vscode-list-inactiveSelectionBackground);
}
.network-request-title-status {
padding: 0 2px;
border-radius: 4px;

View File

@ -22,18 +22,15 @@ import type { Entry } from '@trace/har';
export const NetworkResourceDetails: React.FunctionComponent<{
resource: ResourceSnapshot,
index: number,
selected: boolean,
setSelected: React.Dispatch<React.SetStateAction<number>>,
}> = ({ resource, index, selected, setSelected }) => {
highlighted: boolean,
}> = ({ resource, highlighted }) => {
const [expanded, setExpanded] = React.useState(false);
const [requestBody, setRequestBody] = React.useState<string | null>(null);
const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string } | null>(null);
React.useEffect(() => {
setExpanded(false);
setSelected(-1);
}, [resource, setSelected]);
}, [resource]);
React.useEffect(() => {
const readResources = async () => {
@ -89,7 +86,7 @@ export const NetworkResourceDetails: React.FunctionComponent<{
}, [contentType, resource, resourceName, routeStatus]);
return <div
className={'network-request ' + (selected ? 'selected' : '')} onClick={() => setSelected(index)}>
className={'network-request' + (highlighted ? ' highlighted' : '')}>
<Expandable expanded={expanded} setExpanded={setExpanded} title={ renderTitle() }>
<div className='network-request-details'>
<div className='network-request-details-time'>{resource.time}ms</div>

View File

@ -21,14 +21,18 @@ import { NetworkResourceDetails } from './networkResourceDetails';
import './networkTab.css';
export const NetworkTab: React.FunctionComponent<{
model: modelUtil.MultiTraceModel | undefined,
action: ActionTraceEvent | undefined,
}> = ({ action }) => {
const [selected, setSelected] = React.useState(0);
const resources = action ? modelUtil.resourcesForAction(action) : [];
}> = ({ model, action }) => {
const actionResources = action ? modelUtil.resourcesForAction(action) : [];
const resources = model?.resources || [];
return <div className='network-tab'>{
resources.map((resource, index) => {
return <NetworkResourceDetails resource={resource} key={index} index={index} selected={selected === index} setSelected={setSelected} />;
return <NetworkResourceDetails
resource={resource}
key={index}
highlighted={actionResources.includes(resource)}
/>;
})
}</div>;
};

View File

@ -61,7 +61,7 @@
}
.snapshot-tab:focus .snapshot-toggle.toggled {
background: var(--blue);
background: var(--vscode-charts-blue);
}
.snapshot-wrapper {

View File

@ -88,7 +88,7 @@
.timeline-bar.frame_check,
.timeline-bar.frame_uncheck,
.timeline-bar.frame_tap {
--action-color: var(--green);
--action-color: var(--vscode-charts-green);
}
.timeline-bar.page_load,
@ -110,11 +110,11 @@
.timeline-bar.frame_goback,
.timeline-bar.frame_goforward,
.timeline-bar.reload {
--action-color: var(--blue);
--action-color: var(--vscode-charts-blue);
}
.timeline-bar.frame_evaluateexpression {
--action-color: var(--yellow);
--action-color: var(--vscode-charts-yellow);
}
.timeline-bar.frame_dialog {
@ -122,7 +122,7 @@
}
.timeline-bar.frame_navigated {
--action-color: var(--blue);
--action-color: var(--vscode-charts-blue);
}
.timeline-bar.frame_waitforeventinfo,

View File

@ -19,7 +19,7 @@ import * as React from 'react';
import { ActionList } from './actionList';
import { CallTab } from './callTab';
import { ConsoleTab } from './consoleTab';
import * as modelUtil from './modelUtil';
import type * as modelUtil from './modelUtil';
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
import { NetworkTab } from './networkTab';
import { SnapshotTab } from './snapshotTab';
@ -68,9 +68,6 @@ export const Workbench: React.FunctionComponent<{
onSelectionChanged?.(action);
}, [setSelectedAction, onSelectionChanged]);
const { errors, warnings } = activeAction ? modelUtil.stats(activeAction) : { errors: 0, warnings: 0 };
const consoleCount = errors + warnings;
const networkCount = activeAction ? modelUtil.resourcesForAction(activeAction).length : 0;
const sdkLanguage = model?.sdkLanguage || 'javascript';
const callTab: TabbedPaneTabModel = {
@ -91,19 +88,17 @@ export const Workbench: React.FunctionComponent<{
const consoleTab: TabbedPaneTabModel = {
id: 'console',
title: 'Console',
count: consoleCount,
render: () => <ConsoleTab action={activeAction} />
render: () => <ConsoleTab model={model} action={activeAction} />
};
const networkTab: TabbedPaneTabModel = {
id: 'network',
title: 'Network',
count: networkCount,
render: () => <NetworkTab action={activeAction} />
render: () => <NetworkTab model={model} action={activeAction} />
};
const attachmentsTab: TabbedPaneTabModel = {
id: 'attachments',
title: 'Attachments',
render: () => <AttachmentsTab action={activeAction} />
render: () => <AttachmentsTab model={model} />
};
const tabs: TabbedPaneTabModel[] = showSourcesFirst ? [
@ -136,7 +131,6 @@ export const Workbench: React.FunctionComponent<{
{
id: 'actions',
title: 'Actions',
count: 0,
component: <ActionList
sdkLanguage={sdkLanguage}
actions={model?.actions || []}
@ -150,7 +144,6 @@ export const Workbench: React.FunctionComponent<{
{
id: 'metadata',
title: 'Metadata',
count: 0,
component: <MetadataView model={model}/>
},
]

View File

@ -19,28 +19,13 @@
}
body {
--red: #F44336;
--green: #367c39;
--purple: #9C27B0;
--yellow: #ff9207;
--white: #FFFFFF;
--blue: #0b7ad5;
--transparent-blue: #2196F355;
--orange: #d24726;
--light-pink: #ff69b460;
--gray: #888888;
--sidebar-width: 250px;
--box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
}
body.dark-mode {
--green: #28d12f;
--yellow: #ff9207;
--purple: #dc12ff;
--blue: #4dafff;
--orange: #ff9800;
}
html, body {
width: 100%;
height: 100%;
@ -103,11 +88,15 @@ svg {
}
.codicon-check {
color: var(--green);
color: var(--vscode-charts-green);
}
.codicon-error {
color: var(--red);
color: var(--vscode-errorForeground);
}
.codicon-warning {
color: var(--vscode-list-warningForeground);
}
.codicon-circle-outline {

View File

@ -20,7 +20,7 @@
flex: auto;
position: relative;
user-select: none;
overflow: auto;
overflow-y: auto;
outline: 1px solid transparent;
}
@ -75,3 +75,7 @@
.list-view-entry.error {
color: var(--vscode-list-errorForeground);
}
.list-view-entry.warning {
color: var(--vscode-list-warningForeground);
}

View File

@ -19,18 +19,20 @@ import './listView.css';
export type ListViewProps<T> = {
items: T[],
id?: (item: T) => string,
render: (item: T) => React.ReactNode,
icon?: (item: T) => string | undefined,
indent?: (item: T) => number | undefined,
isError?: (item: T) => boolean,
id?: (item: T, index: number) => string,
render: (item: T, index: number) => React.ReactNode,
icon?: (item: T, index: number) => string | undefined,
indent?: (item: T, index: number) => number | undefined,
isError?: (item: T, index: number) => boolean,
isWarning?: (item: T, index: number) => boolean,
isHighlighted?: (item: T, index: number) => boolean,
selectedItem?: T,
onAccepted?: (item: T) => void,
onSelected?: (item: T) => void,
onLeftArrow?: (item: T) => void,
onRightArrow?: (item: T) => void,
onAccepted?: (item: T, index: number) => void,
onSelected?: (item: T, index: number) => void,
onLeftArrow?: (item: T, index: number) => void,
onRightArrow?: (item: T, index: number) => void,
onHighlighted?: (item: T | undefined) => void,
onIconClicked?: (item: T) => void,
onIconClicked?: (item: T, index: number) => void,
noItemsMessage?: string,
dataTestId?: string,
};
@ -41,6 +43,8 @@ export function ListView<T>({
render,
icon,
isError,
isWarning,
isHighlighted,
indent,
selectedItem,
onAccepted,
@ -63,10 +67,10 @@ export function ListView<T>({
<div
className='list-view-content'
tabIndex={0}
onDoubleClick={() => selectedItem && onAccepted?.(selectedItem)}
onDoubleClick={() => selectedItem && onAccepted?.(selectedItem, items.indexOf(selectedItem))}
onKeyDown={event => {
if (selectedItem && event.key === 'Enter') {
onAccepted?.(selectedItem);
onAccepted?.(selectedItem, items.indexOf(selectedItem));
return;
}
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
@ -76,11 +80,11 @@ export function ListView<T>({
event.preventDefault();
if (selectedItem && event.key === 'ArrowLeft') {
onLeftArrow?.(selectedItem);
onLeftArrow?.(selectedItem, items.indexOf(selectedItem));
return;
}
if (selectedItem && event.key === 'ArrowRight') {
onRightArrow?.(selectedItem);
onRightArrow?.(selectedItem, items.indexOf(selectedItem));
return;
}
@ -102,28 +106,29 @@ export function ListView<T>({
const element = itemListRef.current?.children.item(newIndex);
scrollIntoViewIfNeeded(element || undefined);
onHighlighted?.(undefined);
onSelected?.(items[newIndex]);
onSelected?.(items[newIndex], newIndex);
}}
ref={itemListRef}
>
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
{items.map((item, index) => {
const selectedSuffix = selectedItem === item ? ' selected' : '';
const highlightedSuffix = highlightedItem === item ? ' highlighted' : '';
const errorSuffix = isError?.(item) ? ' error' : '';
const indentation = indent?.(item) || 0;
const rendered = render(item);
const highlightedSuffix = isHighlighted?.(item, index) || highlightedItem === item ? ' highlighted' : '';
const errorSuffix = isError?.(item, index) ? ' error' : '';
const warningSuffix = isWarning?.(item, index) ? ' warning' : '';
const indentation = indent?.(item, index) || 0;
const rendered = render(item, index);
return <div
key={id?.(item) || index}
key={id?.(item, index) || index}
role='listitem'
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix}
onClick={() => onSelected?.(item)}
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix + warningSuffix}
onClick={() => onSelected?.(item, index)}
onMouseEnter={() => setHighlightedItem(item)}
onMouseLeave={() => setHighlightedItem(undefined)}
>
{indentation ? new Array(indentation).fill(0).map(() => <div className='list-view-indent'></div>) : undefined}
{icon && <div
className={'codicon ' + (icon(item) || 'codicon-blank')}
className={'codicon ' + (icon(item, index) || 'codicon-blank')}
style={{ minWidth: 16, marginRight: 4 }}
onDoubleClick={e => {
e.preventDefault();
@ -132,7 +137,7 @@ export function ListView<T>({
onClick={e => {
e.stopPropagation();
e.preventDefault();
onIconClicked?.(item);
onIconClicked?.(item, index);
}}
></div>}
{typeof rendered === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{rendered}</div> : rendered}

View File

@ -51,13 +51,6 @@
display: inline-block;
}
.tabbed-pane-tab-count {
font-size: 10px;
display: flex;
align-self: flex-start;
width: 0px;
}
.tabbed-pane-tab.selected {
background-color: var(--vscode-tab-activeBackground);
}

View File

@ -21,7 +21,6 @@ import * as React from 'react';
export interface TabbedPaneTabModel {
id: string;
title: string | JSX.Element;
count?: number;
component?: React.ReactElement;
render?: () => React.ReactElement;
}
@ -41,7 +40,6 @@ export const TabbedPane: React.FunctionComponent<{
<TabbedPaneTab
id={tab.id}
title={tab.title}
count={tab.count}
selected={selectedTab === tab.id}
onSelect={setSelectedTab}
></TabbedPaneTab>)),
@ -63,14 +61,12 @@ export const TabbedPane: React.FunctionComponent<{
export const TabbedPaneTab: React.FunctionComponent<{
id: string,
title: string | JSX.Element,
count?: number,
selected?: boolean,
onSelect: (id: string) => void
}> = ({ id, title, count, selected, onSelect }) => {
}> = ({ id, title, selected, onSelect }) => {
return <div className={'tabbed-pane-tab ' + (selected ? 'selected' : '')}
onClick={() => onSelect(id)}
key={id}>
<div className='tabbed-pane-tab-label'>{title}</div>
<div className='tabbed-pane-tab-count'>{count || ''}</div>
</div>;
};

View File

@ -1,3 +1,4 @@
<!DOCTYPE html>
<link rel='stylesheet' href='./style.css'>
<script src='./script.js' type='text/javascript'></script>
<style>

View File

@ -32,8 +32,8 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s
const context = await browser.newContext();
await context.tracing.start({ name: 'test', screenshots: true, snapshots: true, sources: true });
const page = await context.newPage();
await page.goto(`data:text/html,<html>Hello world</html>`);
await page.setContent('<button>Click</button>');
await page.goto(`data:text/html,<!DOCTYPE html><html>Hello world</html>`);
await page.setContent('<!DOCTYPE html><button>Click</button>');
await expect(page.locator('button')).toHaveText('Click');
await expect(page.getByTestId('amazing-btn')).toBeHidden();
await expect(page.getByTestId(/amazing-btn-regex/)).toBeHidden();
@ -102,7 +102,7 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await expect(traceViewer.actionTitles).toHaveText([
/browserContext.newPage/,
/page.gotodata:text\/html,<html>Hello world<\/html>/,
/page.gotodata:text\/html,<!DOCTYPE html><html>Hello world<\/html>/,
/page.setContent/,
/expect.toHaveTextlocator\('button'\)/,
/expect.toBeHiddengetByTestId\('amazing-btn'\)/,
@ -135,12 +135,32 @@ test('should render events', async ({ showTraceViewer }) => {
test('should render console', async ({ showTraceViewer, browserName }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('page.evaluate');
await traceViewer.showConsoleTab();
await expect(traceViewer.consoleLineMessages).toHaveText(['Info', 'Warning', 'Error', 'Unhandled exception']);
await expect(traceViewer.consoleLines).toHaveClass(['console-line log', 'console-line warning', 'console-line error', 'console-line error']);
await expect(traceViewer.consoleLineMessages).toHaveText([
'Info',
'Warning',
'Error',
'Unhandled exception',
'Cheers!'
]);
await expect(traceViewer.consoleLines.locator('.codicon')).toHaveClass([
'codicon codicon-blank',
'codicon codicon-warning',
'codicon codicon-error',
'codicon codicon-error',
'codicon codicon-blank',
]);
await expect(traceViewer.consoleStacks.first()).toContainText('Error: Unhandled exception');
await traceViewer.selectAction('page.evaluate');
await expect(traceViewer.page.locator('.console-tab').locator('.list-view-entry')).toHaveClass([
'list-view-entry highlighted',
'list-view-entry highlighted warning',
'list-view-entry highlighted error',
'list-view-entry highlighted error',
'list-view-entry',
]);
});
test('should open console errors on click', async ({ showTraceViewer, browserName }) => {

View File

@ -17,6 +17,28 @@
import { test as it, expect } from './pageTest';
it('console.log', async ({ page }) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
await page.check('input');
expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true);
await page.evaluate(() => {
console.log('1');
console.log('2');
console.log(window);
console.log({ a: 2 });
});
await page.setContent(`<input id='checkbox' type='checkbox' checked></input>`);
await page.check('input');
expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true);
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
await page.uncheck('input');
expect(await page.evaluate(() => window['checkbox'].checked)).toBe(false);
});
it('should check the box @smoke', async ({ page }) => {
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
await page.check('input');