Skip to content

Commit fa229e5

Browse files
committed
Code refactor
1 parent 587d437 commit fa229e5

File tree

6 files changed

+349
-288
lines changed

6 files changed

+349
-288
lines changed

packages/app/src/components/workbench/network.ts

Lines changed: 9 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -3,132 +3,19 @@ import { html, css, nothing } from 'lit'
33
import { customElement, state } from 'lit/decorators.js'
44
import { consume } from '@lit/context'
55
import { networkRequestContext } from '../../controller/DataManager.js'
6+
import { RESOURCE_TYPES } from '../../utils/network-constants.js'
7+
import {
8+
formatBytes,
9+
formatTime,
10+
getStatusClass,
11+
getResourceType,
12+
getFileName
13+
} from '../../utils/network-helpers.js'
614

715
import '../placeholder.js'
816

917
const COMPONENT = 'wdio-devtools-network'
1018

11-
function formatBytes(bytes?: number): string {
12-
if (!bytes || bytes === 0) {
13-
return '-'
14-
}
15-
const k = 1024
16-
const sizes = ['B', 'KB', 'MB', 'GB']
17-
const i = Math.floor(Math.log(bytes) / Math.log(k))
18-
const size = bytes / Math.pow(k, i)
19-
return size >= 10
20-
? `${size.toFixed(0)}${sizes[i]}`
21-
: `${size.toFixed(1)}${sizes[i]}`
22-
}
23-
24-
function formatTime(ms?: number): string {
25-
if (ms === undefined || ms === null) {
26-
return '-'
27-
}
28-
if (ms < 1) {
29-
return `${ms.toFixed(2)}ms`
30-
}
31-
if (ms < 1000) {
32-
return `${ms.toFixed(0)}ms`
33-
}
34-
return `${(ms / 1000).toFixed(1)}s`
35-
}
36-
37-
function getStatusClass(status?: number): string {
38-
if (!status) {
39-
return 'text-gray-500'
40-
}
41-
if (status >= 200 && status < 300) {
42-
return 'text-green-500'
43-
}
44-
if (status >= 300 && status < 400) {
45-
return 'text-yellow-500'
46-
}
47-
if (status >= 400) {
48-
return 'text-red-500'
49-
}
50-
return 'text-gray-500'
51-
}
52-
53-
function getResourceType(request: NetworkRequest): string {
54-
const url = request.url.toLowerCase()
55-
const contentType =
56-
request.responseHeaders?.['content-type']?.toLowerCase() || ''
57-
58-
// Check by content-type first
59-
if (contentType.includes('text/html')) {
60-
return 'HTML'
61-
}
62-
if (contentType.includes('text/css')) {
63-
return 'CSS'
64-
}
65-
if (
66-
contentType.includes('javascript') ||
67-
contentType.includes('ecmascript')
68-
) {
69-
return 'JS'
70-
}
71-
if (contentType.includes('image/')) {
72-
return 'Image'
73-
}
74-
if (contentType.includes('font/') || contentType.includes('woff')) {
75-
return 'Font'
76-
}
77-
if (contentType.includes('application/json')) {
78-
return 'Fetch'
79-
}
80-
81-
// Fallback to URL extension
82-
if (url.endsWith('.html') || url.endsWith('.htm')) {
83-
return 'HTML'
84-
}
85-
if (url.endsWith('.css')) {
86-
return 'CSS'
87-
}
88-
if (url.endsWith('.js') || url.endsWith('.mjs')) {
89-
return 'JS'
90-
}
91-
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp|ico)$/)) {
92-
return 'Image'
93-
}
94-
if (url.match(/\.(woff|woff2|ttf|eot|otf)$/)) {
95-
return 'Font'
96-
}
97-
98-
// Check by request type
99-
if (request.type === 'fetch' || request.method !== 'GET') {
100-
return 'Fetch'
101-
}
102-
103-
return 'Other'
104-
}
105-
106-
function getFileName(url: string): string {
107-
if (!url || url === '' || url === 'event') {
108-
return '-'
109-
}
110-
111-
try {
112-
const urlObj = new URL(url)
113-
const pathname = urlObj.pathname
114-
const parts = pathname.split('/').filter(Boolean)
115-
const fileName = parts[parts.length - 1]
116-
117-
// If there's a query string and no filename, show the host + path
118-
if (!fileName || fileName === '' || pathname === '/') {
119-
if (urlObj.search) {
120-
return `${urlObj.hostname}${pathname.length > 1 ? pathname : ''}`
121-
}
122-
return urlObj.hostname
123-
}
124-
125-
return fileName
126-
} catch {
127-
// If URL parsing fails, return a cleaned version
128-
return url.slice(0, 50)
129-
}
130-
}
131-
13219
@customElement(COMPONENT)
13320
export class DevtoolsNetwork extends Element {
13421
@consume({ context: networkRequestContext, subscribe: true })
@@ -407,7 +294,6 @@ export class DevtoolsNetwork extends Element {
407294

408295
render() {
409296
const filteredRequests = this.#filterRequests()
410-
const resourceTypes = ['All', 'Fetch', 'HTML', 'JS', 'CSS', 'Font', 'Image']
411297

412298
if (!this.networkRequests || this.networkRequests.length === 0) {
413299
return html`
@@ -430,7 +316,7 @@ export class DevtoolsNetwork extends Element {
430316
(this.searchQuery = (e.target as HTMLInputElement).value)}"
431317
/>
432318
<div class="filter-tabs">
433-
${resourceTypes.map(
319+
${RESOURCE_TYPES.map(
434320
(type) => html`
435321
<button
436322
class="filter-tab ${this.filterType === type ? 'active' : ''}"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Resource type patterns for network request classification
3+
*/
4+
export const RESOURCE_TYPE_PATTERNS = {
5+
HTML: {
6+
contentTypes: ['text/html'],
7+
extensions: ['.html', '.htm']
8+
},
9+
CSS: {
10+
contentTypes: ['text/css'],
11+
extensions: ['.css']
12+
},
13+
JS: {
14+
contentTypes: ['javascript', 'ecmascript'],
15+
extensions: ['.js', '.mjs']
16+
},
17+
Image: {
18+
contentTypes: ['image/'],
19+
extensions: ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico']
20+
},
21+
Font: {
22+
contentTypes: ['font/', 'woff'],
23+
extensions: ['.woff', '.woff2', '.ttf', '.eot', '.otf']
24+
},
25+
Fetch: {
26+
contentTypes: ['application/json'],
27+
extensions: []
28+
}
29+
} as const
30+
31+
/**
32+
* Available resource types for filtering
33+
*/
34+
export const RESOURCE_TYPES = [
35+
'All',
36+
'Fetch',
37+
'HTML',
38+
'JS',
39+
'CSS',
40+
'Font',
41+
'Image'
42+
] as const
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { RESOURCE_TYPE_PATTERNS } from './network-constants.js'
2+
3+
/**
4+
* Format bytes to human-readable format
5+
*/
6+
export function formatBytes(bytes?: number): string {
7+
if (!bytes || bytes === 0) {
8+
return '-'
9+
}
10+
const k = 1024
11+
const sizes = ['B', 'KB', 'MB', 'GB']
12+
const i = Math.floor(Math.log(bytes) / Math.log(k))
13+
const size = bytes / Math.pow(k, i)
14+
return size >= 10
15+
? `${size.toFixed(0)}${sizes[i]}`
16+
: `${size.toFixed(1)}${sizes[i]}`
17+
}
18+
19+
/**
20+
* Format milliseconds to human-readable format
21+
*/
22+
export function formatTime(ms?: number): string {
23+
if (ms === undefined || ms === null) {
24+
return '-'
25+
}
26+
if (ms < 1) {
27+
return `${ms.toFixed(2)}ms`
28+
}
29+
if (ms < 1000) {
30+
return `${ms.toFixed(0)}ms`
31+
}
32+
return `${(ms / 1000).toFixed(1)}s`
33+
}
34+
35+
/**
36+
* Get CSS class based on HTTP status code
37+
*/
38+
export function getStatusClass(status?: number): string {
39+
if (!status) {
40+
return 'text-gray-500'
41+
}
42+
if (status >= 200 && status < 300) {
43+
return 'text-green-500'
44+
}
45+
if (status >= 300 && status < 400) {
46+
return 'text-yellow-500'
47+
}
48+
if (status >= 400) {
49+
return 'text-red-500'
50+
}
51+
return 'text-gray-500'
52+
}
53+
54+
/**
55+
* Determine resource type from network request
56+
*/
57+
export function getResourceType(request: NetworkRequest): string {
58+
const url = request.url.toLowerCase()
59+
const contentType =
60+
request.responseHeaders?.['content-type']?.toLowerCase() || ''
61+
62+
// Check by content-type first
63+
for (const [type, patterns] of Object.entries(RESOURCE_TYPE_PATTERNS)) {
64+
if (patterns.contentTypes.some((ct) => contentType.includes(ct))) {
65+
return type
66+
}
67+
}
68+
69+
// Fallback to URL extension
70+
for (const [type, patterns] of Object.entries(RESOURCE_TYPE_PATTERNS)) {
71+
if (patterns.extensions.some((ext) => url.endsWith(ext))) {
72+
return type
73+
}
74+
}
75+
76+
// Check by request type
77+
if (request.type === 'fetch' || request.method !== 'GET') {
78+
return 'Fetch'
79+
}
80+
81+
return 'Other'
82+
}
83+
84+
/**
85+
* Extract filename from URL
86+
*/
87+
export function getFileName(url: string): string {
88+
if (!url || url === '' || url === 'event') {
89+
return '-'
90+
}
91+
92+
try {
93+
const urlObj = new URL(url)
94+
const pathname = urlObj.pathname
95+
const parts = pathname.split('/').filter(Boolean)
96+
const fileName = parts[parts.length - 1]
97+
98+
// If there's a query string and no filename, show the host + path
99+
if (!fileName || fileName === '' || pathname === '/') {
100+
if (urlObj.search) {
101+
return `${urlObj.hostname}${pathname.length > 1 ? pathname : ''}`
102+
}
103+
return urlObj.hostname
104+
}
105+
106+
return fileName
107+
} catch {
108+
// If URL parsing fails, return a cleaned version
109+
return url.slice(0, 50)
110+
}
111+
}

packages/service/src/types.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ export interface Metadata {
2525
viewport: VisualViewport
2626
}
2727

28+
export interface NetworkRequest {
29+
id: string
30+
url: string
31+
method: string
32+
status?: number
33+
statusText?: string
34+
type: string
35+
initiator?: string
36+
size?: number
37+
time?: number
38+
requestHeaders?: Record<string, string>
39+
responseHeaders?: Record<string, string>
40+
requestBody?: string
41+
responseBody?: string
42+
timestamp: number
43+
startTime: number
44+
endTime?: number
45+
error?: string
46+
}
47+
2848
export interface TraceLog {
2949
mutations: TraceMutation[]
3050
logs: string[]
@@ -64,7 +84,7 @@ export interface ServiceOptions {
6484
devtoolsCapabilities?: WebdriverIO.Capabilities
6585
}
6686

67-
declare module WebdriverIO {
87+
declare namespace WebdriverIO {
6888
interface ServiceOption extends ServiceOptions {}
6989
interface Capabilities {}
7090
}

0 commit comments

Comments
 (0)