Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/grid-ui/src/tests/components/Overview.test.tsx
2887 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
import * as React from 'react'
19
import { render, screen, within } from '@testing-library/react'
20
import userEvent from '@testing-library/user-event'
21
import '@testing-library/jest-dom'
22
import { MockedProvider } from '@apollo/client/testing'
23
import Overview from '../../screens/Overview/Overview'
24
import { NODES_QUERY } from '../../graphql/nodes'
25
import { GRID_SESSIONS_QUERY } from '../../graphql/sessions'
26
27
jest.mock('../../components/LiveView/LiveView', () => {
28
return {
29
__esModule: true,
30
default: React.forwardRef((props: { url: string, scaleViewport?: boolean, onClose?: () => void }, ref) => {
31
React.useImperativeHandle(ref, () => ({
32
disconnect: jest.fn()
33
}))
34
return <div data-testid="mock-live-view" data-url={props.url}>LiveView Mock</div>
35
})
36
}
37
})
38
39
const mockNodesData = {
40
nodesInfo: {
41
nodes: [
42
{
43
id: 'node1',
44
uri: 'http://192.168.1.10:4444',
45
status: 'UP',
46
maxSession: 5,
47
slotCount: 5,
48
version: '4.0.0',
49
osInfo: {
50
name: 'Linux',
51
version: '5.4.0',
52
arch: 'x86_64'
53
},
54
sessionCount: 1,
55
stereotypes: JSON.stringify([
56
{
57
stereotype: {
58
browserName: 'chrome',
59
browserVersion: '88.0',
60
platformName: 'linux'
61
},
62
slots: 5
63
}
64
])
65
},
66
{
67
id: 'node2',
68
uri: 'http://192.168.1.11:4444',
69
status: 'UP',
70
maxSession: 5,
71
slotCount: 5,
72
version: '4.0.0',
73
osInfo: {
74
name: 'Windows',
75
version: '10',
76
arch: 'x86_64'
77
},
78
sessionCount: 2,
79
stereotypes: JSON.stringify([
80
{
81
stereotype: {
82
browserName: 'firefox',
83
browserVersion: '78.0',
84
platformName: 'windows'
85
},
86
slots: 5
87
}
88
])
89
}
90
]
91
}
92
}
93
94
const mockSessionsData = {
95
sessionsInfo: {
96
sessions: [
97
{
98
id: 'session1',
99
nodeId: 'node1',
100
capabilities: JSON.stringify({
101
browserName: 'chrome',
102
browserVersion: '88.0',
103
platformName: 'linux',
104
'se:vnc': 'ws://192.168.1.10:5900/websockify'
105
}),
106
startTime: '2023-01-01T00:00:00Z',
107
uri: 'http://192.168.1.10:4444/session/session1',
108
nodeUri: 'http://192.168.1.10:4444',
109
sessionDurationMillis: 60000,
110
slot: {
111
id: 'slot1',
112
stereotype: '{"browserName":"chrome"}',
113
lastStarted: '2023-01-01T00:00:00Z'
114
}
115
},
116
{
117
id: 'session2',
118
nodeId: 'node2',
119
capabilities: JSON.stringify({
120
browserName: 'firefox',
121
browserVersion: '78.0',
122
platformName: 'windows'
123
}),
124
startTime: '2023-01-01T00:00:00Z',
125
uri: 'http://192.168.1.11:4444/session/session2',
126
nodeUri: 'http://192.168.1.11:4444',
127
sessionDurationMillis: 60000,
128
slot: {
129
id: 'slot2',
130
stereotype: '{"browserName":"firefox"}',
131
lastStarted: '2023-01-01T00:00:00Z'
132
}
133
}
134
],
135
sessionQueueRequests: []
136
}
137
}
138
139
const mocks = [
140
{
141
request: {
142
query: NODES_QUERY
143
},
144
result: {
145
data: mockNodesData
146
}
147
},
148
{
149
request: {
150
query: GRID_SESSIONS_QUERY
151
},
152
result: {
153
data: mockSessionsData
154
}
155
},
156
{
157
request: {
158
query: GRID_SESSIONS_QUERY
159
},
160
result: {
161
data: mockSessionsData
162
}
163
},
164
{
165
request: {
166
query: GRID_SESSIONS_QUERY
167
},
168
result: {
169
data: mockSessionsData
170
}
171
}
172
]
173
174
describe('Overview component', () => {
175
beforeEach(() => {
176
Object.defineProperty(window, 'location', {
177
value: {
178
origin: 'http://localhost:4444'
179
},
180
writable: true
181
})
182
})
183
184
it('renders loading state initially', () => {
185
render(
186
<MockedProvider mocks={mocks} addTypename={false}>
187
<Overview />
188
</MockedProvider>
189
)
190
191
expect(screen.getByRole('progressbar')).toBeInTheDocument()
192
})
193
194
it('renders nodes when data is loaded', async () => {
195
render(
196
<MockedProvider mocks={mocks} addTypename={false}>
197
<Overview />
198
</MockedProvider>
199
)
200
201
await screen.findByText('http://192.168.1.10:4444')
202
203
expect(screen.getByText('http://192.168.1.10:4444')).toBeInTheDocument()
204
expect(screen.getByText('http://192.168.1.11:4444')).toBeInTheDocument()
205
})
206
207
it('renders sort controls', async () => {
208
render(
209
<MockedProvider mocks={mocks} addTypename={false}>
210
<Overview />
211
</MockedProvider>
212
)
213
214
await screen.findByText('http://192.168.1.10:4444')
215
216
expect(screen.getAllByText('Sort By').length).toBeGreaterThan(0)
217
expect(screen.getByText('Descending')).toBeInTheDocument()
218
})
219
220
it('changes sort option when selected', async () => {
221
render(
222
<MockedProvider mocks={mocks} addTypename={false}>
223
<Overview />
224
</MockedProvider>
225
)
226
227
await screen.findByText('http://192.168.1.10:4444')
228
229
const user = userEvent.setup()
230
const selectElement = screen.getByRole('combobox')
231
await user.click(selectElement)
232
233
await user.click(screen.getByText('Browser Name'))
234
235
expect(selectElement).toHaveTextContent('Browser Name')
236
})
237
238
it('toggles sort order when descending checkbox is clicked', async () => {
239
render(
240
<MockedProvider mocks={mocks} addTypename={false}>
241
<Overview />
242
</MockedProvider>
243
)
244
245
await screen.findByText('http://192.168.1.10:4444')
246
247
const user = userEvent.setup()
248
const descendingLabel = screen.getByText('Descending')
249
const checkbox = descendingLabel.closest('label')?.querySelector('input[type="checkbox"]')
250
251
expect(checkbox).not.toBeNull()
252
if (checkbox) {
253
await user.click(checkbox)
254
expect(checkbox).toBeChecked()
255
}
256
})
257
258
it('sorts nodes by URI when selected', async () => {
259
render(
260
<MockedProvider mocks={mocks} addTypename={false}>
261
<Overview />
262
</MockedProvider>
263
)
264
265
await screen.findByText('http://192.168.1.10:4444')
266
267
const user = userEvent.setup()
268
const selectElement = screen.getByRole('combobox')
269
await user.click(selectElement)
270
await user.click(screen.getByText('URI'))
271
272
// After sorting by URI, node1 should appear before node2
273
const nodeUris = screen.getAllByText(/http:\/\/192\.168\.1\.\d+:4444/)
274
expect(nodeUris[0]).toHaveTextContent('http://192.168.1.10:4444')
275
expect(nodeUris[1]).toHaveTextContent('http://192.168.1.11:4444')
276
})
277
278
it('sorts nodes by URI in descending order when selected', async () => {
279
render(
280
<MockedProvider mocks={mocks} addTypename={false}>
281
<Overview />
282
</MockedProvider>
283
)
284
285
await screen.findByText('http://192.168.1.10:4444')
286
287
const user = userEvent.setup()
288
const selectElement = screen.getByRole('combobox')
289
await user.click(selectElement)
290
await user.click(screen.getByText('URI'))
291
292
const descendingLabel = screen.getByText('Descending')
293
const checkbox = descendingLabel.closest('label')?.querySelector('input[type="checkbox"]')
294
expect(checkbox).not.toBeNull()
295
if (checkbox) {
296
await user.click(checkbox)
297
expect(checkbox).toBeChecked()
298
}
299
300
// After sorting by URI descending, node2 should appear before node1
301
const nodeUris = screen.getAllByText(/http:\/\/192\.168\.1\.\d+:4444/)
302
expect(nodeUris[0]).toHaveTextContent('http://192.168.1.11:4444')
303
expect(nodeUris[1]).toHaveTextContent('http://192.168.1.10:4444')
304
})
305
306
it('renders live view icon for node with VNC session', async () => {
307
render(
308
<MockedProvider mocks={mocks} addTypename={false}>
309
<Overview />
310
</MockedProvider>
311
)
312
313
await screen.findByText('http://192.168.1.10:4444')
314
315
expect(screen.getByTestId('VideocamIcon')).toBeInTheDocument()
316
})
317
318
it('does not render live view icon for node without VNC session', async () => {
319
render(
320
<MockedProvider mocks={mocks} addTypename={false}>
321
<Overview />
322
</MockedProvider>
323
)
324
325
await screen.findByText('http://192.168.1.11:4444')
326
327
const videocamIcons = screen.getAllByTestId('VideocamIcon')
328
329
expect(videocamIcons.length).toBe(1)
330
331
const node2Element = screen.getByText('http://192.168.1.11:4444')
332
const node2Card = node2Element.closest('.MuiCard-root')
333
334
if (node2Card) {
335
expect(within(node2Card as HTMLElement).queryByTestId('VideocamIcon')).not.toBeInTheDocument()
336
}
337
})
338
339
it('opens live view dialog when camera icon is clicked', async () => {
340
render(
341
<MockedProvider mocks={mocks} addTypename={false}>
342
<Overview />
343
</MockedProvider>
344
)
345
346
await screen.findByText('http://192.168.1.10:4444')
347
348
const user = userEvent.setup()
349
await user.click(screen.getByTestId('VideocamIcon'))
350
351
expect(screen.getByText('Node Session Live View')).toBeInTheDocument()
352
expect(screen.getByTestId('mock-live-view')).toBeInTheDocument()
353
})
354
355
it('closes live view dialog when close button is clicked', async () => {
356
render(
357
<MockedProvider mocks={mocks} addTypename={false}>
358
<Overview />
359
</MockedProvider>
360
)
361
362
await screen.findByText('http://192.168.1.10:4444')
363
364
const user = userEvent.setup()
365
await user.click(screen.getByTestId('VideocamIcon'))
366
367
expect(screen.getByText('Node Session Live View')).toBeInTheDocument()
368
369
await user.click(screen.getByRole('button', { name: /close/i }))
370
371
expect(screen.queryByText('Node Session Live View')).not.toBeInTheDocument()
372
})
373
374
it('handles error state', async () => {
375
const errorMocks = [
376
{
377
request: {
378
query: NODES_QUERY
379
},
380
error: new Error('Network error')
381
},
382
{
383
request: {
384
query: GRID_SESSIONS_QUERY
385
},
386
result: {
387
data: mockSessionsData
388
}
389
},
390
{
391
request: {
392
query: GRID_SESSIONS_QUERY
393
},
394
result: {
395
data: mockSessionsData
396
}
397
}
398
]
399
400
render(
401
<MockedProvider mocks={errorMocks} addTypename={false}>
402
<Overview />
403
</MockedProvider>
404
)
405
406
await new Promise(resolve => setTimeout(resolve, 0))
407
408
const errorElement = screen.getByRole('heading', { level: 3 })
409
expect(errorElement).toBeInTheDocument()
410
})
411
412
it('handles empty nodes state', async () => {
413
const emptyMocks = [
414
{
415
request: {
416
query: NODES_QUERY
417
},
418
result: {
419
data: { nodesInfo: { nodes: [] } }
420
}
421
},
422
{
423
request: {
424
query: GRID_SESSIONS_QUERY
425
},
426
result: {
427
data: mockSessionsData
428
}
429
},
430
{
431
request: {
432
query: GRID_SESSIONS_QUERY
433
},
434
result: {
435
data: mockSessionsData
436
}
437
}
438
]
439
440
render(
441
<MockedProvider mocks={emptyMocks} addTypename={false}>
442
<Overview />
443
</MockedProvider>
444
)
445
446
await screen.findByText('The Grid has no registered Nodes yet.')
447
448
expect(screen.getByText('The Grid has no registered Nodes yet.')).toBeInTheDocument()
449
})
450
})
451
452