diff --git a/ui/e2e/init.setup.ts b/ui/e2e/init.setup.ts index fee7c17dc3..c56e0d5a16 100644 --- a/ui/e2e/init.setup.ts +++ b/ui/e2e/init.setup.ts @@ -41,7 +41,7 @@ setup('initialize vault and setup user for testing', async ({ page, userType }) await page.getByRole('button', { name: 'Sign in' }).click(); // create a policy for a specific user persona // defaults to superuser but should be passed in via the project config in playwright.config.ts - await page.getByRole('link', { name: 'Access', exact: true }).click(); + await page.getByRole('link', { name: 'Access control', exact: true }).click(); await page.getByRole('link', { name: 'Create ACL policy' }).click(); await page.getByRole('textbox', { name: 'Policy name' }).fill(userType); await page.getByRole('radio', { name: 'Code editor' }).check(); diff --git a/ui/e2e/pages/base.ts b/ui/e2e/pages/base.ts new file mode 100644 index 0000000000..6f576e4e2a --- /dev/null +++ b/ui/e2e/pages/base.ts @@ -0,0 +1,19 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { Page } from '@playwright/test'; + +export class BasePage { + constructor(protected page: Page) {} + // remove all flash messages by clicking the dismiss button until there are no more + // this is useful if many are rendered over top of a button preventing click in a test + async dismissFlashMessages() { + const locator = this.page.getByRole('button', { name: 'Dismiss' }); + // use a while loop because clicking one might cause the next one to shift or re-render. + while ((await locator.count()) > 0) { + await locator.first().click(); + } + } +} diff --git a/ui/e2e/tests/superuser/kv.spec.ts b/ui/e2e/tests/superuser/kv.spec.ts index f53b4d5c91..f9c8cd0d63 100644 --- a/ui/e2e/tests/superuser/kv.spec.ts +++ b/ui/e2e/tests/superuser/kv.spec.ts @@ -4,11 +4,13 @@ */ import { test, expect } from '@playwright/test'; +import { BasePage } from '../../pages/base'; test('kvv2 workflow', async ({ page }) => { + const basePage = new BasePage(page); await page.goto('dashboard'); // enable kv secrets engine - await page.getByRole('link', { name: 'Secrets Engines' }).click(); + await page.getByRole('link', { name: 'Secrets', exact: true }).click(); await page.getByRole('link', { name: 'Enable new engine' }).click(); await page.locator('div').filter({ hasText: 'KV' }).nth(4).click(); await page.getByRole('textbox', { name: 'Path' }).click(); @@ -20,7 +22,7 @@ test('kvv2 workflow', async ({ page }) => { 'No secrets yet When created, secrets will be listed here. Create a secret to get started.' ); // verify that the kv engine appears in the list view - await page.getByRole('link', { name: 'Secrets Engines' }).click(); + await page.getByLabel('Secrets Navigation Links').getByRole('link', { name: 'Secrets engines' }).click(); await page.getByRole('link', { name: 'kv-test/' }).click(); // create a secret await page.getByRole('link', { name: 'Create secret' }).click(); @@ -54,6 +56,8 @@ test('kvv2 workflow', async ({ page }) => { await page.getByRole('link', { name: 'Version History' }).click(); await expect(page.locator('section')).toContainText('Version 1'); await expect(page.locator('section')).toContainText('Current'); + // there are a stack of flash messages that are blocking the manage version button from being clicked + await basePage.dismissFlashMessages(); await page.getByRole('button', { name: 'Manage version' }).click(); await page.getByRole('link', { name: 'Create new version from 1', exact: true }).click(); await page.getByRole('textbox', { name: 'key' }).first().fill('bar-v2'); diff --git a/ui/e2e/tests/superuser/tools.spec.ts b/ui/e2e/tests/superuser/tools.spec.ts new file mode 100644 index 0000000000..3a5f1e0d9d --- /dev/null +++ b/ui/e2e/tests/superuser/tools.spec.ts @@ -0,0 +1,69 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { test, expect } from '@playwright/test'; +import { BasePage } from '../../pages/base'; + +test('tools workflow', async ({ page }) => { + const basePage = new BasePage(page); + await page.goto('dashboard'); + // wrap data + await page.getByRole('link', { name: 'Operational tools' }).click(); + await page.getByRole('navigation', { name: 'toolbar filters' }).locator('label').click(); + await page.getByRole('textbox', { name: 'Key' }).fill('foo'); + await page.getByRole('textbox', { name: 'Value' }).fill('bar'); + await page.locator('label').filter({ hasText: 'Wrap TTL Vault will use the' }).click(); + await page.getByRole('textbox', { name: 'Number of units' }).fill('20'); + await page.getByLabel('ttl-unit').selectOption('h'); + await page.getByRole('button', { name: 'Wrap data' }).click(); + await page.getByRole('button', { name: 'copy hvs.' }).click(); + // access the clipboard to get the copied token value + let clipboardValue = await page.evaluate(() => navigator.clipboard.readText()); + // lookup token + await page.getByRole('link', { name: 'Lookup' }).click(); + await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue); + await page.getByRole('button', { name: 'Lookup token' }).click(); + await expect(page.getByRole('heading', { name: 'Lookup token' })).toContainText('Lookup token'); + await expect(page.locator('section')).toContainText('about 20 hours'); + // rewrap token + await page.getByRole('link', { name: 'Rewrap' }).click(); + await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue); + await page.getByRole('button', { name: 'Rewrap token' }).click(); + await expect(page.locator('label')).toContainText('Rewrapped token'); + await page.getByRole('button', { name: 'copy hvs.' }).click(); + // access the clipboard to get the rewrapped token value + clipboardValue = await page.evaluate(() => navigator.clipboard.readText()); + // unwrap token + await page.getByRole('link', { name: 'Unwrap' }).click(); + await page.getByRole('textbox', { name: 'Wrapped token' }).fill(clipboardValue); + await page.getByRole('button', { name: 'Unwrap data' }).click(); + await expect(page.getByRole('code')).toContainText('{ "foo": "bar" }'); + // generate random bytes + await page.getByRole('link', { name: 'Random' }).click(); + await page.getByRole('spinbutton', { name: 'Number of bytes' }).fill('64'); + await page.getByLabel('Output format').selectOption('hex'); + await page.getByRole('button', { name: 'Generate' }).click(); + await expect(page.getByRole('button', { name: 'copy' })).toBeVisible(); + // hash + await page.getByRole('link', { name: 'Hash', exact: true }).click(); + await page.getByRole('textbox', { name: 'Input' }).fill('foobar'); + await page.getByLabel('Algorithm').selectOption('sha2-512'); + await page.getByLabel('Output format').selectOption('hex'); + // there are a stack of flash messages that are blocking the encode button from being clicked + await basePage.dismissFlashMessages(); + await page.getByRole('button', { name: 'Encode to base64' }).click(); + await page.getByRole('button', { name: 'Hash' }).click(); + await expect(page.getByLabel('copy')).toContainText( + '0a50261ebd1a390fed2bf326f2673c145582a6342d523204973d0219337f81616a8069b012587cf5635f6925f1b56c360230c19b273500ee013e030601bf2425' + ); + // API explorer + await page.getByRole('link', { name: 'API explorer' }).click(); + await expect(page.getByRole('heading', { name: 'API explorer' })).toContainText('API explorer'); + await page.getByRole('textbox', { name: 'Filter by tag' }).fill('lookup-accessor'); + await expect(page.locator('#operations-0-tokenLookUpAccessor')).toContainText( + '/auth/token/lookup-accessor' + ); + await expect(page.locator('#operations-0-tokenLookUpAccessor')).toContainText('tokenLookUpAccessor'); +}); diff --git a/ui/playwright.config.ts b/ui/playwright.config.ts index 75529c9e67..fddaf1d8aa 100644 --- a/ui/playwright.config.ts +++ b/ui/playwright.config.ts @@ -67,6 +67,7 @@ export default defineConfig({ storageState: fs.existsSync(sessionFile) ? sessionFile : undefined, // start at port 8204 and increment for each project to allow them to run concurrently without conflicts baseURL: getURL(index), + permissions: ['clipboard-read', 'clipboard-write'], }, }; }),