Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/playwright-core/browsers.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
},
{
"name": "chromium-tip-of-tree",
"revision": "1404",
"revision": "1405",
"installByDefault": false,
"browserVersion": "146.0.7657.0",
"browserVersion": "146.0.7669.0",
"title": "Chrome Canary for Testing"
},
{
"name": "chromium-tip-of-tree-headless-shell",
"revision": "1404",
"revision": "1405",
"installByDefault": false,
"browserVersion": "146.0.7657.0",
"browserVersion": "146.0.7669.0",
"title": "Chrome Canary Headless Shell"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export type SourceModel = {
content: string | undefined;
};

export type ResourceEntry = ResourceSnapshot & { id: string };

export type ActionTraceEventInContext = ActionEntry & {
context: ContextEntry;
};
Expand Down Expand Up @@ -84,7 +86,7 @@ export class TraceModel {
readonly sdkLanguage: Language | undefined;
readonly testIdAttributeName: string | undefined;
readonly sources: Map<string, SourceModel>;
resources: ResourceSnapshot[];
resources: ResourceEntry[];
readonly actionCounters: Map<string, number>;
readonly traceUri: string;

Expand Down Expand Up @@ -113,7 +115,7 @@ export class TraceModel {
this.errors = ([] as trace.ErrorTraceEvent[]).concat(...contexts.map(c => c.errors));
this.hasSource = contexts.some(c => c.hasSource);
this.hasStepData = contexts.some(context => context.origin === 'testRunner');
this.resources = [...contexts.map(c => c.resources)].flat();
this.resources = [...contexts.map(c => c.resources)].flat().map(entry => ({ ...entry, id: `${entry.pageref}-${entry.time}-${entry.request.url}` }));
this.attachments = this.actions.flatMap(action => action.attachments?.map(attachment => ({ ...attachment, callId: action.callId, traceUri })) ?? []);
this.visibleAttachments = this.attachments.filter(attachment => !attachment.name.startsWith('_'));

Expand Down
27 changes: 13 additions & 14 deletions packages/trace-viewer/src/ui/networkTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
* limitations under the License.
*/

import type { Entry } from '@trace/har';
import * as React from 'react';
import type { Boundaries } from './geometry';
import './networkTab.css';
import { NetworkResourceDetails } from './networkResourceDetails';
import { bytesToString, msToString } from '@web/uiUtils';
import { PlaceholderPanel } from './placeholderPanel';
import { context } from '@isomorphic/trace/traceModel';
import { context, type ResourceEntry } from '@isomorphic/trace/traceModel';
import type { TraceModel } from '@isomorphic/trace/traceModel';
import { GridView, type RenderedGridCell } from '@web/components/gridView';
import { SplitView } from '@web/components/splitView';
Expand All @@ -30,7 +29,7 @@ import { NetworkFilters, defaultFilterState, type FilterState, type ResourceType
import type { Language } from '@isomorphic/locatorGenerators';

type NetworkTabModel = {
resources: Entry[],
resources: ResourceEntry[],
contextIdMap: ContextIdMap,
};

Expand All @@ -44,7 +43,7 @@ type RenderedEntry = {
size: number,
start: number,
route: string,
resource: Entry,
resource: ResourceEntry,
contextId: string,
};
type ColumnName = keyof RenderedEntry;
Expand Down Expand Up @@ -72,25 +71,25 @@ export const NetworkTab: React.FunctionComponent<{
sdkLanguage: Language,
}> = ({ boundaries, networkModel, onResourceHovered, sdkLanguage }) => {
const [sorting, setSorting] = React.useState<Sorting | undefined>(undefined);
const [selectedEntry, setSelectedEntry] = React.useState<RenderedEntry | undefined>(undefined);
const [selectedResourceKey, setSelectedResourceKey] = React.useState<string | undefined>(undefined);
const [filterState, setFilterState] = React.useState(defaultFilterState);

const visibleSelectedEntry = React.useMemo(() => (selectedEntry && networkModel.resources.includes(selectedEntry.resource)) ? selectedEntry : undefined, [selectedEntry, networkModel.resources]);

const { renderedEntries } = React.useMemo(() => {
const renderedEntries = networkModel.resources.map((entry, i) => renderEntry(entry, boundaries, networkModel.contextIdMap, i)).filter(filterEntry(filterState));
if (sorting)
sort(renderedEntries, sorting);
return { renderedEntries };
}, [networkModel.resources, networkModel.contextIdMap, filterState, sorting, boundaries]);

const visibleSelectedEntry = React.useMemo(() => (selectedResourceKey ? renderedEntries.find(entry => entry.resource.id === selectedResourceKey) : undefined), [selectedResourceKey, renderedEntries]);

const [columnWidths, setColumnWidths] = React.useState<Map<ColumnName, number>>(() => {
return new Map(allColumns().map(column => [column, columnWidth(column)]));
});

const onFilterStateChange = React.useCallback((newFilterState: FilterState) => {
setFilterState(newFilterState);
setSelectedEntry(undefined);
setSelectedResourceKey(undefined);
}, []);

if (!networkModel.resources.length)
Expand All @@ -101,7 +100,7 @@ export const NetworkTab: React.FunctionComponent<{
ariaLabel='Network requests'
items={renderedEntries}
selectedItem={visibleSelectedEntry}
onSelected={item => setSelectedEntry(item)}
onSelected={item => setSelectedResourceKey(item.resource.id)}
onHighlighted={item => onResourceHovered?.(item?.ordinal)}
columns={visibleColumns(!!visibleSelectedEntry, renderedEntries)}
columnTitle={columnTitle}
Expand All @@ -122,7 +121,7 @@ export const NetworkTab: React.FunctionComponent<{
sidebarIsFirst={true}
orientation='horizontal'
settingName='networkResourceDetails'
main={<NetworkResourceDetails resource={visibleSelectedEntry.resource} sdkLanguage={sdkLanguage} startTimeOffset={visibleSelectedEntry.start} onClose={() => setSelectedEntry(undefined)} />}
main={<NetworkResourceDetails resource={visibleSelectedEntry.resource} sdkLanguage={sdkLanguage} startTimeOffset={visibleSelectedEntry.start} onClose={() => setSelectedResourceKey(undefined)} />}
sidebar={grid}
/>}
</>;
Expand Down Expand Up @@ -223,7 +222,7 @@ class ContextIdMap {

constructor(model: TraceModel | undefined) {}

contextId(resource: Entry): string {
contextId(resource: ResourceEntry): string {
if (resource.pageref)
return this._pageId(resource.pageref);
else if (resource._apiRequest)
Expand All @@ -241,7 +240,7 @@ class ContextIdMap {
return shortId;
}

private _apiRequestContextId(resource: Entry): string {
private _apiRequestContextId(resource: ResourceEntry): string {
const contextEntry = context(resource);
if (!contextEntry)
return '';
Expand All @@ -265,7 +264,7 @@ function hasMultipleContexts(renderedEntries: RenderedEntry[]): boolean {
return false;
}

const renderEntry = (resource: Entry, boundaries: Boundaries, contextIdGenerator: ContextIdMap, ordinal: number): RenderedEntry => {
const renderEntry = (resource: ResourceEntry, boundaries: Boundaries, contextIdGenerator: ContextIdMap, ordinal: number): RenderedEntry => {
const routeStatus = formatRouteStatus(resource);
let resourceName: string;
try {
Expand Down Expand Up @@ -298,7 +297,7 @@ const renderEntry = (resource: Entry, boundaries: Boundaries, contextIdGenerator
};
};

function formatRouteStatus(request: Entry): string {
function formatRouteStatus(request: ResourceEntry): string {
if (request._wasAborted)
return 'aborted';
if (request._wasContinued)
Expand Down
24 changes: 12 additions & 12 deletions tests/playwright-test/stable-test-runner/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/playwright-test/stable-test-runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"private": true,
"dependencies": {
"@playwright/test": "^1.59.0-alpha-2026-02-02"
"@playwright/test": "^1.59.0-alpha-2026-02-09"
}
}
29 changes: 26 additions & 3 deletions tests/playwright-test/ui-mode-test-network-tab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,6 @@ test('should not preserve selection across test runs', async ({ runUITest, serve
import { test, expect } from '@playwright/test';
test('network tab test', async ({ page }) => {
await page.goto('${server.PREFIX}/network-tab/network.html');
await page.evaluate(() => (window as any).donePromise);
});
`,
});
Expand All @@ -383,11 +382,35 @@ test('should not preserve selection across test runs', async ({ runUITest, serve
await expect(page.getByTestId('workbench-run-status')).toContainText('Passed');

await page.getByRole('tab', { name: 'Network' }).click();
const networkItem = page.getByRole('listitem').filter({ hasText: 'network.html' });
await networkItem.click();
await page.getByRole('listitem').filter({ hasText: 'network.html' }).click();
const headersPanel = page.getByRole('tabpanel', { name: 'Headers' });
await expect(headersPanel).toBeVisible();

await page.getByRole('treeitem', { name: 'network tab test' }).dblclick();
await expect(headersPanel).toBeHidden();
await expect(page.getByTestId('workbench-run-status')).toContainText('Passed');
await expect(headersPanel).toBeHidden();
});

test('should preserve selection during test run', async ({ runUITest, server }, testInfo) => {
const { page } = await runUITest({
'network-tab.test.ts': `
import { test, expect } from '@playwright/test';
test('network tab test', async ({ page }) => {
await page.goto('${server.PREFIX}/network-tab/network.html');
// Keep test running to make sure that selected network entry stay open
await page.waitForTimeout(${testInfo.timeout});
});
`,
});

await page.getByRole('treeitem', { name: 'network tab test' }).dblclick();
await page.getByRole('tab', { name: 'Network' }).click();
await page.getByRole('listitem').filter({ hasText: 'network.html' }).click();
const headersPanel = page.getByRole('tabpanel', { name: 'Headers' });
await expect(headersPanel).toBeVisible();

// Wait to ensure that trace polling (every 500ms) does not close the selected entry
await page.waitForTimeout(1000);
await expect(headersPanel).toBeVisible();
});
22 changes: 11 additions & 11 deletions utils/flakiness-dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading