-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathinstance-create.e2e.ts
614 lines (511 loc) · 27 KB
/
instance-create.e2e.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright Oxide Computer Company
*/
import { floatingIp } from '@oxide/api-mocks'
import {
closeToast,
expect,
expectNotVisible,
expectRowVisible,
expectVisible,
selectOption,
sleep,
test,
type Page,
} from './utils'
const selectASiloImage = async (page: Page, name: string) => {
await page.getByRole('tab', { name: 'Silo images' }).click()
await page.getByPlaceholder('Select a silo image', { exact: true }).click()
await page.getByRole('option', { name }).click()
}
const selectAProjectImage = async (page: Page, name: string) => {
await page.getByRole('tab', { name: 'Project images' }).click()
await page.getByPlaceholder('Select a project image', { exact: true }).click()
await page.getByRole('option', { name }).click()
}
const selectAnExistingDisk = async (page: Page, name: string) => {
await page.getByRole('tab', { name: 'Existing disks' }).click()
await page.getByRole('combobox', { name: 'Disk' }).click()
await page.getByRole('option', { name }).click()
}
test('can create an instance', async ({ page }) => {
await page.goto('/projects/mock-project/instances')
await page.locator('text="New Instance"').click()
await expect(page.getByRole('heading', { name: /Create instance/ })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Hardware' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Boot disk' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Additional disks' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Networking' })).toBeVisible()
await expect(page.getByRole('textbox', { name: 'Description' })).toBeVisible()
await expect(page.getByRole('textbox', { name: 'Disk size (GiB)' })).toBeVisible()
const instanceName = 'my-instance'
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await page.fill('textarea[name=description]', 'An instance... from space!')
await page.locator('.ox-radio-card').nth(3).click()
await page.getByRole('textbox', { name: 'Disk name' }).fill('my-boot-disk')
const diskSizeInput = page.getByRole('textbox', { name: 'Disk size (GiB)' })
await diskSizeInput.fill('20')
// pick a project image just to show we can
await selectAProjectImage(page, 'image-3')
// should be hidden in accordion
await expectNotVisible(page, [
'role=radiogroup[name="Network interface"]',
'role=textbox[name="Hostname"]',
'text="User Data"',
])
// open networking and config accordions
await page.getByRole('button', { name: 'Networking' }).click()
await page.getByRole('button', { name: 'Configuration' }).click()
const checkbox = page.getByRole('checkbox', {
name: 'Allocate and attach an ephemeral IP address',
})
const label = page.getByLabel('IP pool for ephemeral IP')
// verify that the ip pool selector is visible and default is selected
await expect(checkbox).toBeChecked()
await label.click()
await expect(page.getByRole('option', { name: 'ip-pool-1' })).toBeEnabled()
// unchecking the box should disable the selector
await checkbox.uncheck()
await expect(label).toBeHidden()
// re-checking the box should re-enable the selector, and other options should be selectable
await checkbox.check()
await selectOption(page, 'IP pool for ephemeral IP', 'ip-pool-2 VPN IPs')
// should be visible in accordion
await expect(page.getByRole('radiogroup', { name: 'Network interface' })).toBeVisible()
// we show the default hostname, instance name, as placeholder
await expect(page.getByRole('textbox', { name: 'Hostname' })).toHaveAttribute(
'placeholder',
instanceName
)
await expect(page.getByLabel('User data')).toBeVisible()
await page.getByRole('button', { name: 'Create instance' }).click()
await closeToast(page)
// instance goes from creating to starting to running as we poll
const pollingSpinner = page.getByLabel('Spinner')
await expect(pollingSpinner).toBeVisible()
await expect(page.getByText('Creating')).toBeVisible()
await expect(page.getByText('Starting')).toBeVisible()
await expect(page.getByText('Running')).toBeVisible()
await expect(pollingSpinner).toBeHidden()
// do this after state checks because sometimes it takes too long and we miss 'creating'
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expect(page.getByRole('heading', { name: instanceName })).toBeVisible()
await expect(page.getByText('16 vCPUs')).toBeVisible()
await expect(page.getByText('64 GiB')).toBeVisible()
await expect(page.getByText('from space')).toBeVisible()
// boot disk visible, no other disks attached
await expect(
page
.getByRole('table', { name: 'Boot disk' })
.getByRole('cell', { name: 'my-boot-disk' })
).toBeVisible()
await expect(page.getByText('No other disk')).toBeVisible()
// network tab works
await page.getByRole('tab', { name: 'Networking' }).click()
const table = page.getByRole('table', { name: 'Network interfaces' })
await expectRowVisible(table, {
name: 'defaultprimary',
vpc: 'mock-vpc',
subnet: 'mock-subnet',
})
})
test('duplicate instance name produces visible error', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await page.fill('input[name=name]', 'db1')
await selectAProjectImage(page, 'image-1')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page.getByText('Instance name already exists')).toBeVisible()
})
test('first preset is auto-selected in each tab', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await expect(page.getByRole('radio', { name: '2 CPU 8 gibibytes RAM' })).toBeChecked()
await page.getByRole('tab', { name: 'High CPU' }).click()
await expect(page.getByRole('radio', { name: '2 CPU 4 gibibytes RAM' })).toBeChecked()
await page.getByRole('tab', { name: 'High Memory' }).click()
await expect(page.getByRole('radio', { name: '2 CPU 16 gibibytes RAM' })).toBeChecked()
await page.getByRole('tab', { name: 'General Purpose' }).click()
await expect(page.getByRole('radio', { name: '2 CPU 8 gibibytes RAM' })).toBeChecked()
})
test('can create an instance with custom hardware', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
const instanceName = 'my-custom-instance'
await page.fill('input[name=name]', instanceName)
await page.fill('textarea[name=description]', 'An instance... from space!')
// Click the other tabs to make sure the custom input works
// even when something has been previously selected
await page.getByRole('tab', { name: 'High CPU' }).click()
await page.getByRole('tab', { name: 'High Memory' }).click()
await page.getByText('64 GiB RAM').click()
// Fill in custom specs
await page.getByRole('tab', { name: 'Custom' }).click()
await page.getByRole('textbox', { name: 'CPUs' }).fill('29')
await page.getByRole('textbox', { name: 'Memory (GiB)' }).fill('53')
await page.getByRole('textbox', { name: 'Disk name' }).fill('my-boot-disk')
const diskSizeInput = page.getByRole('textbox', { name: 'Disk size (GiB)' })
await diskSizeInput.fill('20')
await page.keyboard.press('Tab')
// pick a project image just to show we can
await selectAProjectImage(page, 'image-3')
// the disk size should bot have been changed from what was entered earlier
await expect(diskSizeInput).toHaveValue('20')
// test disk size validation against image size
// the minimum on the number input will be the size of the image (6GiB),
// so manually entering a number less than that will be corrected
await diskSizeInput.fill('5')
await page.keyboard.press('Tab')
await expect(diskSizeInput).toHaveValue('6')
const submitButton = page.getByRole('button', { name: 'Create instance' })
await submitButton.click() // submit to trigger validation
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expect(page.getByRole('heading', { name: instanceName })).toBeVisible()
await expect(page.getByText('29 vCPUs')).toBeVisible()
await expect(page.getByText('53 GiB')).toBeVisible()
await expect(page.getByText('from space')).toBeVisible()
})
test('automatically updates disk size when larger image selected', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
const instanceName = 'my-new-instance'
await page.fill('input[name=name]', instanceName)
// set the disk size larger than it needs to be, to verify it doesn't get reduced
const diskSizeInput = page.getByRole('textbox', { name: 'Disk size (GiB)' })
await diskSizeInput.fill('5')
await page.keyboard.press('Tab')
// pick a disk image that's smaller than 5GiB (the first project image works [4GiB])
await selectAProjectImage(page, 'image-1')
// test that it still says 5, as that's larger than the given image
await expect(diskSizeInput).toHaveValue('5')
// pick a disk image that's larger than 5GiB (the third project image works [6GiB])
await selectAProjectImage(page, 'image-3')
// test that it has been automatically increased to next-largest incremement of 10
await expect(diskSizeInput).toHaveValue('10')
// pick another image, just to verify that the diskSizeInput stays as it was
await selectAProjectImage(page, 'image-2')
await expect(diskSizeInput).toHaveValue('10')
const submitButton = page.getByRole('button', { name: 'Create instance' })
await submitButton.click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`, 'text=10 GiB'])
})
test('with disk name already taken', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await page.fill('input[name=name]', 'my-instance')
await selectAProjectImage(page, 'image-1')
await page.fill('input[name=bootDiskName]', 'disk-1')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page.getByText('Name is already in use').first()).toBeVisible()
})
test('can’t create a disk with a name that collides with the boot disk name', async ({
page,
}) => {
// Set up the instance and name the boot disk "disk-11"
await page.goto('/projects/mock-project/instances-new')
await page.fill('input[name=name]', 'another-instance')
await selectAProjectImage(page, 'image-1')
await page.fill('input[name=bootDiskName]', 'disk-11')
// Attempt to create a disk with the same name
await page.getByRole('button', { name: 'Create new disk' }).click()
const dialog = page.getByRole('dialog')
await dialog.getByRole('textbox', { name: 'name' }).fill('disk-11')
await dialog.getByRole('button', { name: 'Create disk' }).click()
// Expect to see an error message
await expect(dialog.getByText('Name is already in use')).toBeVisible()
// Change the disk name to something else
await dialog.getByRole('textbox', { name: 'name' }).fill('disk-12')
await dialog.getByRole('button', { name: 'Create disk' }).click()
// The disk has been "created" (is in the list of Additional Disks)
await expectVisible(page, ['text=disk-12'])
// Create the instance
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL('/projects/mock-project/instances/another-instance/storage')
// Find the Boot Disk table and verify that disk-11 is there
const bootDiskTable = page.getByRole('table', { name: 'Boot disk' })
await expect(bootDiskTable.getByRole('cell', { name: 'disk-11' })).toBeVisible()
// Find the Other Disks table and verify that disk-12 is there
const otherDisksTable = page.getByRole('table', { name: 'Additional disks' })
await expect(otherDisksTable.getByRole('cell', { name: 'disk-12' })).toBeVisible()
})
test('add ssh key from instance create form', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await expect(page.getByRole('checkbox', { name: 'm1-macbook-pro' })).toBeChecked()
await expect(page.getByRole('checkbox', { name: 'mac-mini' })).toBeChecked()
const newKey = 'new-key'
const newCheckbox = page.getByRole('checkbox', { name: newKey })
await expect(newCheckbox).toBeHidden()
// open model, fill form, and submit
const dialog = page.getByRole('dialog')
await page.getByRole('button', { name: 'Add SSH Key' }).click()
await dialog.getByRole('textbox', { name: 'Name' }).fill(newKey)
await dialog.getByRole('textbox', { name: 'Description' }).fill('hi')
await dialog.getByRole('textbox', { name: 'Public key' }).fill('some stuff, whatever')
await dialog.getByRole('button', { name: 'Add SSH Key' }).click()
await expect(newCheckbox).toBeVisible()
await expect(newCheckbox).not.toBeChecked()
// pop over to the real SSH keys page and see it there, why not
await page.getByLabel('User menu').click()
await page.getByRole('menuitem', { name: 'Settings' }).click()
await page.getByRole('link', { name: 'SSH Keys' }).click()
await expectRowVisible(page.getByRole('table'), { name: newKey, description: 'hi' })
})
test('shows object not found error on no default pool', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('no-default-pool')
await selectAProjectImage(page, 'image-1')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page.getByText('Not found: default IP pool for current silo')).toBeVisible()
})
test('create instance with existing disk', async ({ page }) => {
const instanceName = 'my-existing-disk-instance'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await selectAnExistingDisk(page, 'disk-3')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`, 'text=8 GiB'])
const bootDisks = page.getByRole('table', { name: 'Boot disk' })
await expectRowVisible(bootDisks, { Disk: 'disk-3' })
await expect(page.getByText('No other disks')).toBeVisible()
})
test('create instance with a silo image', async ({ page }) => {
const instanceName = 'my-existing-disk-2'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await selectASiloImage(page, 'ubuntu-22-04')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`, 'text=10 GiB'])
})
test('start with an existing disk, but then switch to a silo image', async ({ page }) => {
const instanceName = 'silo-image-instance'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await selectAnExistingDisk(page, 'disk-7')
await selectASiloImage(page, 'ubuntu-22-04')
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`, 'text=8 GiB'])
await expectNotVisible(page, ['text=disk-7'])
})
test('additional disks do not list committed disks as available', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
const attachExistingDiskButton = page.getByRole('button', {
name: 'Attach existing disk',
})
const selectADisk = page.getByPlaceholder('Select a disk')
const disk2 = page.getByRole('option', { name: 'disk-2' })
const disk3 = page.getByRole('option', { name: 'disk-3' })
const disk4 = page.getByRole('option', { name: 'disk-4' })
await attachExistingDiskButton.click()
await selectADisk.click()
// disk-2 is already attached, so should not be visible in the list
await expect(disk2).toBeHidden()
// disk-3, though, should be present
await expect(disk3).toBeVisible()
await expect(disk4).toBeVisible()
// select disk-3 and "attach" it to the instance that will be created
await disk3.click()
await page.getByRole('button', { name: 'Attach disk' }).click()
await attachExistingDiskButton.click()
await selectADisk.click()
// disk-2 should still be hidden
await expect(disk2).toBeHidden()
// now disk-3 should be hidden as well
await expect(disk3).toBeHidden()
await expect(disk4).toBeVisible()
})
test('maintains selected values even when changing tabs', async ({ page }) => {
const instanceName = 'arch-based-instance'
const arch = 'arch-2022-06-01'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
const imageSelectCombobox = page.getByRole('combobox', { name: 'Image' })
// Filter the combobox for a particular silo image
await imageSelectCombobox.fill('arch')
// select the image
await page.getByRole('option', { name: arch }).click()
// expect to find name of the image on page
await expect(imageSelectCombobox).toHaveValue(arch)
// change to a different tab
await page.getByRole('tab', { name: 'Existing disks' }).click()
// the image should no longer be visible
await expect(imageSelectCombobox).toBeHidden()
// change back to the tab with the image
await page.getByRole('tab', { name: 'Silo images' }).click()
// arch should still be selected
await expect(imageSelectCombobox).toHaveValue(arch)
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`, 'text=8 GiB'])
// when a disk name isn’t assigned, the generated one uses the name of the image,
// so this checks to make sure that the arch-based image — with name `arch-2022-06-01` — was used
await expectVisible(page, [`text=${instanceName}-${arch}`])
})
test('does not attach an ephemeral IP when the checkbox is unchecked', async ({ page }) => {
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('no-ephemeral-ip')
await selectAProjectImage(page, 'image-1')
await page.getByRole('button', { name: 'Networking' }).click()
await page
.getByRole('checkbox', { name: 'Allocate and attach an ephemeral IP address' })
.uncheck()
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL('/projects/mock-project/instances/no-ephemeral-ip/storage')
await expect(page.getByText('External IPs—')).toBeVisible()
})
test('attaches a floating IP; disables button when no IPs available', async ({ page }) => {
const attachFloatingIpButton = page.getByRole('button', { name: 'Attach floating IP' })
const dialog = page.getByRole('dialog')
const selectFloatingIpButton = dialog.getByRole('button', { name: 'Floating IP' })
const rootbeerFloatOption = page.getByRole('option', { name: 'rootbeer-float' })
const attachButton = dialog.getByRole('button', { name: 'Attach', exact: true })
const instanceName = 'with-floating-ip'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await selectAProjectImage(page, 'image-1')
await page.getByRole('button', { name: 'Networking' }).click()
await attachFloatingIpButton.click()
await expect(
page.getByText('This instance will be reachable at the selected IP')
).toBeVisible()
await selectFloatingIpButton.click()
await rootbeerFloatOption.click()
await expect(
page.getByText('This instance will be reachable at 123.4.56.4')
).toBeVisible()
await attachButton.click()
await expect(page.getByText('This instance will be reachable at')).toBeHidden()
await expectRowVisible(page.getByRole('table'), {
Name: floatingIp.name,
IP: floatingIp.ip,
})
await expect(attachFloatingIpButton).toBeDisabled()
// removing the floating IP row should work, and should re-enable the "attach" button
await page.getByRole('button', { name: 'remove floating IP rootbeer-float' }).click()
await expect(page.getByText(floatingIp.name)).toBeHidden()
await expect(attachFloatingIpButton).toBeEnabled()
// re-attach the floating IP
await attachFloatingIpButton.click()
await selectFloatingIpButton.click()
await rootbeerFloatOption.click()
await attachButton.click()
await page.getByRole('button', { name: 'Create instance' }).click()
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
await expectVisible(page, [`h1:has-text("${instanceName}")`])
await page.getByRole('tab', { name: 'Networking' }).click()
// ensure External IPs table has rows for the Ephemeral IP and the Floating IP
const ipsTable = page.getByRole('table', { name: 'External IPs' })
await expectRowVisible(ipsTable, {
ip: '123.4.56.0',
Kind: 'ephemeral',
name: '—',
})
await expectRowVisible(ipsTable, {
ip: floatingIp.ip,
Kind: 'floating',
name: floatingIp.name,
})
})
test('attach a floating IP section has Empty version when no floating IPs exist on the project', async ({
page,
}) => {
await page.goto('/projects/other-project/instances-new')
await page.getByRole('button', { name: 'Networking' }).click()
await expect(page.getByRole('button', { name: 'Attach floating IP' })).toBeHidden()
await expect(
page.getByText('Create a floating IP to attach it to this instance')
).toBeVisible()
})
test('attaching additional disks allows for combobox filtering', async ({ page }) => {
await page.goto('/projects/other-project/instances-new')
const attachExistingDiskButton = page.getByRole('button', {
name: 'Attach existing disk',
})
const selectADisk = page.getByPlaceholder('Select a disk')
await attachExistingDiskButton.click()
await selectADisk.click()
// several disks should be shown
await expect(page.getByRole('option', { name: 'disk-0001' })).toBeVisible()
await expect(page.getByRole('option', { name: 'disk-0002' })).toBeVisible()
await expect(page.getByRole('option', { name: 'disk-1000' })).toBeVisible()
// type in a string to use as a filter
await selectADisk.fill('disk-010')
// only disks with that substring should be shown
await expect(page.getByRole('option', { name: 'disk-0100' })).toBeVisible()
await expect(page.getByRole('option', { name: 'disk-0101' })).toBeVisible()
await expect(page.getByRole('option', { name: 'disk-0102' })).toBeVisible()
await expect(page.getByRole('option', { name: 'disk-0001' })).toBeHidden()
await expect(page.getByRole('option', { name: 'disk-1000' })).toBeHidden()
// select one
await page.getByRole('option', { name: 'disk-0102' }).click()
// now options hidden and only the selected one is visible in the button/input
await expect(page.getByRole('option')).toBeHidden()
await expect(page.getByRole('combobox', { name: 'Disk name' })).toHaveValue('disk-0102')
// a random string should give a disabled option
await selectADisk.click()
await selectADisk.fill('asdf')
await expect(page.getByRole('option', { name: 'No items match' })).toBeVisible()
})
test('create instance with additional disks', async ({ page }) => {
const instanceName = 'more-disks'
await page.goto('/projects/mock-project/instances-new')
await page.getByRole('textbox', { name: 'Name', exact: true }).fill(instanceName)
await selectAProjectImage(page, 'image-1')
// Create a new disk
await page.getByRole('button', { name: 'Create new disk' }).click()
const createForm = page.getByRole('dialog', { name: 'Create disk' })
await expect(createForm).toBeVisible() // kill time to help size field flake?
// verify that an existing name can't be used
await createForm.getByRole('textbox', { name: 'Name', exact: true }).fill('disk-6')
// If we try to fill the size field too soon after render (unnaturally fast --
// a real user would not be able to do it), the value gets quickly overwritten
// back to the default of 10, possibly because there are renders already in
// flight by the type we fill. This causes test flakes where the field is 10
// after we've filled 5 and the disk we're creating ends up with 10 GiB in
// the table. The flakes happened in Safari, but by adding a sleep _after_ the
// fill but before the check, we can force the failure in every browser. By
// waiting a bit here _before_ the fill, we give those renders a chance to
// wrap up before we fill.
//
// This is a HACK -- logging in instance create and disk create shows that
// disk create does seem to render again a few hundred ms after the initial
// one, and it appears driven by renders in instance create (specifically the
// usePrefetchedApiQuery calls), but I wasn't able to fix it for real.
await sleep(1000)
const sizeField = createForm.getByRole('textbox', { name: 'Size (GiB)' })
await sizeField.fill('5')
await expect(sizeField).toHaveValue('5')
await createForm.getByRole('button', { name: 'Create disk' }).click()
await expect(createForm.getByText('Name is already in use')).toBeVisible()
// rename the disk to one that's allowed
await createForm.getByRole('textbox', { name: 'Name', exact: true }).fill('new-disk-1')
await createForm.getByRole('button', { name: 'Create disk' }).click()
const disksTable = page.getByRole('table', { name: 'Disks' })
await expect(disksTable.getByText('disk-6')).toBeHidden()
await expectRowVisible(disksTable, { Name: 'new-disk-1', Type: 'create', Size: '5GiB' })
// now that name is taken too, so disk create disallows it
await page.getByRole('button', { name: 'Create new disk' }).click()
await createForm.getByRole('textbox', { name: 'Name', exact: true }).fill('new-disk-1')
await createForm.getByRole('button', { name: 'Create disk' }).click()
await expect(createForm.getByText('Name is already in use')).toBeVisible()
await createForm.getByRole('button', { name: 'Cancel' }).click()
// Attach an existing disk
await page.getByRole('button', { name: 'Attach existing disk' }).click()
await selectOption(page, 'Disk name', 'disk-3')
await page.getByRole('button', { name: 'Attach disk' }).click()
await expectRowVisible(disksTable, { Name: 'disk-3', Type: 'attach', Size: '—' })
// Create the instance
await page.getByRole('button', { name: 'Create instance' }).click()
// Assert we're on the new instance's storage page
await expect(page).toHaveURL(`/projects/mock-project/instances/${instanceName}/storage`)
// Check for the boot disk
const bootDiskTable = page.getByRole('table', { name: 'Boot disk' })
// name is generated so it's gnarly
await expect(bootDiskTable.getByRole('cell', { name: /^more-disks-/ })).toBeVisible()
// Check for the additional disks
const otherDisksTable = page.getByRole('table', { name: 'Additional disks' })
await expectRowVisible(otherDisksTable, { Disk: 'new-disk-1', size: '5 GiB' })
await expectRowVisible(otherDisksTable, { Disk: 'disk-3', size: '6 GiB' })
})